Skip to content

Portainer Alternative — Migrate to Dockmesh

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.

Shortcut for the common case: If your Portainer stacks are already running as regular compose projects on the host (Portainer manages them via docker compose), you can skip the manual redeploy and use dmctl stack adopt to hand management over to dockmesh with zero downtime. Come back here if you want the full redeploy-from-scratch migration path instead.

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 | sudo 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 dockmesh server, save it to /var/lib/dockmesh/stacks/<stack-name>/compose.yaml (host-neutral path — the deploy step binds it to a host)

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 > /var/lib/dockmesh/stacks/$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. Users → Roles tab → New role — define custom roles by picking permissions from the live resource.verb catalogue, plus optional role-level scope rows (host_tag = team-frontend, stack = monitoring, etc.)
  2. Authentication → pick a protocol (OIDC, OAuth2, SAML, or LDAP) → Add provider — point to Azure AD, Google, Keycloak, or your provider of choice
  3. In the same provider form, add Group mappings — each row is (group_value, role_name) and the role can be any built-in or custom role you’ve defined. First match wins; non-matches fall through to the provider’s default role

See RBAC docs and SSO docs for details.

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

  1. Agents → New agent in dockmesh — get an enrolment 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.