Skip to content

Migrate from Portainer

This guide walks you through migrating a production Portainer deployment to dockmesh. The migration is non-destructive — both tools can run side-by-side during the transition, and you can roll back at any point before you remove Portainer.

Common reasons self-hosters and teams move from Portainer to dockmesh:

  • RBAC locked behind Portainer Business — dockmesh ships custom roles in the free binary
  • SSO/OIDC locked behind Portainer Business — dockmesh supports Azure AD, Google, Keycloak, Okta, Authentik free
  • No native stack migration between hosts in Portainer — dockmesh moves stacks with volume transfer + rollback
  • No auto-scaling in Portainer — dockmesh has threshold-based scaling with safety checks
  • Single-binary deployment — dockmesh is one ~15 MB file; Portainer requires its own container + volume + upgrades

Check these prerequisites on your Portainer server:

  • Docker 20.10+ with API 1.41+
  • Access to /var/run/docker.sock
  • Your compose.yaml files (or the ability to export them from Portainer)
  • A volume backup strategy — even though migration is non-destructive, always back up first

Step 1 — Install dockmesh alongside Portainer

Section titled “Step 1 — Install dockmesh alongside Portainer”

dockmesh and Portainer don’t conflict. They can share the same Docker daemon.

On your Portainer server, install dockmesh:

Terminal window
curl -fsSL https://get.dockmesh.dev | bash

By default, dockmesh binds to :8080. If Portainer is already using 8080, set a different port:

/etc/systemd/system/dockmesh.service
Environment="DOCKMESH_HTTP_ADDR=:8090"

Restart:

Terminal window
systemctl daemon-reload
systemctl restart dockmesh

Open http://your-server:8090. Both Portainer and dockmesh now see the same containers.

Step 2 — Export your stacks from Portainer

Section titled “Step 2 — Export your stacks from Portainer”

Portainer stores stacks as Compose files. You need to get those files into dockmesh’s stack directory.

Option A — Stacks created via “Web editor” in Portainer

Section titled “Option A — Stacks created via “Web editor” in Portainer”
  1. Open Portainer → Stacks → pick a stack
  2. Click the Editor tab
  3. Copy the full YAML
  4. On the server, save it to /opt/dockmesh/stacks/<host>/<stack-name>/compose.yaml

Repeat for every stack.

Option B — Stacks created via Git repository in Portainer

Section titled “Option B — Stacks created via Git repository in Portainer”

Even easier — dockmesh also supports Git-backed stacks. In dockmesh Stacks → New stack → Git, enter the same repo URL and branch, and dockmesh will pull the compose file directly.

Option C — Bulk export via Portainer API

Section titled “Option C — Bulk export via Portainer API”

For 10+ stacks, scripting saves time:

Terminal window
# Get all stacks
curl -H "X-API-Key: $PORTAINER_KEY" https://portainer.example/api/stacks \
| jq -r '.[] | "\(.Id) \(.Name)"'
# For each stack, pull the compose file
for id in $(curl -s -H "X-API-Key: $PORTAINER_KEY" \
https://portainer.example/api/stacks | jq -r '.[].Id'); do
name=$(curl -s -H "X-API-Key: $PORTAINER_KEY" \
https://portainer.example/api/stacks/$id | jq -r .Name)
curl -s -H "X-API-Key: $PORTAINER_KEY" \
"https://portainer.example/api/stacks/$id/file" \
| jq -r .StackFileContent > /opt/dockmesh/stacks/local/$name/compose.yaml
done

For containers that were started via docker run or manually (not Compose stacks), use dockmesh’s docker run importer:

  1. In Portainer or via CLI, get the docker run equivalent: docker inspect <container> has most of what you need. Community tools like runlike generate a clean one-line command.
  2. In dockmesh: Stacks → New stack → Import from docker run
  3. Paste the command — dockmesh generates the compose.yaml

This is usually the main reason for the migration. In dockmesh:

  1. Settings → Roles → New role — create custom roles with granular permissions
  2. Settings → Authentication → OIDC → Add provider — point to Azure AD, Google, Keycloak, or any OIDC provider
  3. Settings → Group mappings — map IdP group claims to dockmesh roles

See RBAC docs and SSO docs for details.

If you had multiple Portainer Edge agents, switch them to dockmesh agents:

  1. Hosts → Add host in dockmesh — get an enrollment token
  2. On each remote host, run the agent install command the UI generates
  3. dockmesh agents connect outbound via mTLS — same networking model as Portainer Edge, but all agent features are free

You can keep the old Portainer Edge agents running — they don’t conflict. Remove them after everything is stable.

For 1-2 weeks, run both tools side-by-side. Use dockmesh for all new stack changes, and keep Portainer read-only as a fallback view.

Checks before removing Portainer:

  • All stacks visible in dockmesh
  • Deploy, stop, restart all work from dockmesh UI
  • Users have been migrated to new roles
  • SSO works for your team
  • Backups are configured and have completed at least one successful run

Once you’re confident:

Terminal window
docker stop portainer
docker rm portainer
docker volume rm portainer_data # deletes Portainer's database, not your container data

Your container volumes and compose files are untouched — they’re managed by Docker and the filesystem, not Portainer.

Portainer supports Compose v2 and v3 with its own extensions. dockmesh uses the standard Compose spec — the 99% overlap means most files work unchanged. The exceptions:

  • Portainer-specific labels (io.portainer.*) are ignored — safe to leave or remove
  • Portainer template variables ({{.Values.foo}}) need to be resolved — dockmesh uses .env files

When dockmesh deploys a stack, Docker creates volumes using the project name. If your Portainer stack was named analytics_prod, the volume is analytics_prod_pgdata. dockmesh (when creating a new stack with the same name) will reuse these volumes — your data stays put. Just keep the stack name identical.

Portainer calls a Docker host an “endpoint”. dockmesh calls it a host. The enrollment flow is the same outbound-mTLS pattern, just different terminology.

If something’s not working, just use Portainer as before. dockmesh only reads the same Docker socket — it doesn’t mutate Portainer’s database. Stop/remove the dockmesh binary and you’re back to pure Portainer.