Blog
Deploying Docker Swarm Stacks Through Portainer: When the UI Earns Its Place
Portainer's Swarm stack deployment turns opaque SSH-and-CLI deploys into a shared, auditable workflow. Here's how it works, a concrete example, and where it falls short.
Published by Tasadduq Burney
26 Feb 2026, 10:56 UTC
4 min18.6K views0

Your team runs a Docker Swarm cluster, and every stack change means someone SSHing into a manager node, scp-ing a Compose file, and running
docker stack deploy by hand. It works, but nobody else on the team can see what's running, and the last person to deploy is the only one who knows which version of the file is live. Portainer's Swarm stack deployment feature exists to close exactly that gap — and used with a bit of discipline, it genuinely does.
The thesis here is simple: Portainer doesn't replace the Compose file or the Docker CLI, it replaces the opacity. The Compose file stays your source of truth; Portainer becomes the shared window into what that truth currently looks like on the cluster.
What Portainer actually does with your Compose file
When you open the Stacks section in Portainer, point it at a Swarm endpoint, and paste or upload adocker-compose.yml, Portainer translates the Compose directives into Swarm service definitions and submits them through the Docker Engine API. From Docker's perspective, the result is the same as running docker stack deploy on a manager node — the stack shows up in docker stack ls either way.
Standard Compose constructs carry over well: replicas, placement constraints, environment variables, published ports, and image tags all map cleanly. Where you need to be careful is with advanced constructs. Nested or custom-driver networks, Docker secrets, and some configs may not be fully exposed in the UI flow. If your stack depends on those, create the networks and secrets once via the CLI (or in Portainer's dedicated sections), then reference them as external in the Compose file.
A worked example: a small web stack
Suppose you're deploying a reverse proxy and an app service. The Compose file looks like this:version: "3.8"
services:
proxy:
image: nginx:1.25
ports:
- "80:80"
deploy:
replicas: 2
placement:
constraints:
- node.role == worker
app:
image: registry.example.com/myapp:1.4.2
environment:
- APP_ENV=production
deploy:
replicas: 3
In the Portainer UI (you'll need an account with access to the Swarm environment — typically an administrator or a user granted stack permissions on that endpoint):
- Select the Swarm environment, then go to Stacks → Add stack.
- Give the stack a name, e.g.
webfrontend, and paste the Compose content into the web editor (or use the upload option). - Click Deploy the stack and watch the service list populate.
docker group or sudo):
docker stack ls
docker stack services webfrontend
You should see the webfrontend stack and its two services with the expected replica counts (e.g., 2/2 and 3/3 once tasks converge). If the replica column shows 0/3, check the service logs — Portainer exposes these per service, or use docker service logs webfrontend_app from the manager.
Updates, diffs, and the rollback button
Updating is where the workflow earns its keep. Edit the stack in Portainer — say, bumpmyapp:1.4.2 to 1.5.0 — and redeploy. Portainer re-submits the stack definition and Swarm performs its usual rolling update, replacing tasks with the new configuration. The UI shows service status as tasks cycle, so the whole team can watch the rollout instead of one person watching a terminal.
Portainer also keeps previous stack definitions, and the editor view lets you compare what changed before redeploying. If a deploy goes wrong, re-deploying the prior definition restores the previous configuration — functionally a rollback, since Swarm will roll services back to the old image and settings. This is a real state-changing operation: it re-creates service tasks, so treat it with the same care as the original deploy.
The trade-offs you should accept up front
There are three honest limitations. First, coverage: the UI doesn't expose every advanced Compose or Swarm option, so teams with heavy secrets or custom networking needs will still touch the CLI. Second, overwrite risk: anyone with stack permissions can redeploy and clobber running services. Mitigate this by keeping Compose files in version control and treating Portainer's editor as a deployment mechanism, not the canonical store. Third, access scope: Portainer talks to the Docker Engine API with broad privileges. Restrict who can reach the Portainer instance, use role-based access on environments, and rotate any tokens or credentials involved. Also keep an eye on version alignment — Portainer's Swarm support tracks Docker Engine behavior, so after a major Engine upgrade, verify a test stack deploys cleanly before touching production.A practical way to adopt it
Start with one non-critical stack. Deploy it through Portainer, then cross-check withdocker stack services <name> on a manager node to confirm the UI and the cluster agree. Make one small change — an image tag bump — redeploy, and confirm the rolling update completes with no failed tasks. If both checks pass, you've validated the workflow end to end, and you can start moving the rest of your stacks over with the Compose files safely in git as your audit trail.0 replies
A thoughtful contribution can make all the difference. Be the first to share one.