Why self-host a deployment interface on a VPS
A bare VPS with Docker leaves you with one interface: the command line. That works for one machine, gets tedious for five, and becomes unmanageable for a team. A web interface centralises logs, image updates, environment variables and TLS certificates — without forcing you to maintain a full CI/CD pipeline from day one.
Two families compete: managers (Portainer, Yacht) that expose Docker as-is, and lightweight PaaS (Dokploy, Coolify, CapRover) that add a deployment layer — Git branches, automatic domains, Let's Encrypt certificates. The difference is not cosmetic: it determines whether your developers can deploy on their own or need SSH access.
What you gain by deploying this template on your VPS
- Deploy from Git or Docker Hub in a few clicks, no SSH required
- Automatic TLS certificate management via Let's Encrypt
- Versioned environment variables, isolated per project
- Centralised logs and web terminal from your browser
- Health dashboard: CPU, RAM, container status
- Manage Docker volumes and networks without memorising commands
Portainer in 2026: the end of the Community Edition
Portainer CE (Community Edition) was the reference tool for years. With Portainer 3, the vendor ended the Community Edition: Portainer 3 has no CE edition, and CE 2.45 is the last Community release. Portainer 3 ships as Business Edition (free for up to three nodes with the "3 Nodes Free" licence), puts Kubernetes first for new features and keeps Docker, Swarm and Podman. CE 2.45 instances keep running and receive the 2.45 LTS fixes until August 2027, but no new features.
This shift has several practical consequences. New installations looking for a free Docker interface have no CE roadmap. The portainer/portainer-ce images still exist and receive fixes from the 2.45 branch, but will not evolve further. And critically — Portainer 2.x has accumulated critical flaws, including two CVEs scoring 9.4, which remain exploitable on any unpatched installation.
If you are still running Portainer 2.x, the question is not whether to migrate on principle: it is whether your instance is up to date and whether the risk is acceptable.
Critical Portainer 2.x CVEs: check and patch
Two CVEs affect every Portainer branch up to the fixed versions below (versions before 2.33.0 are end-of-life and will not be fixed):
CVE-2026-44848 (CVSS 9.4) — The /plugins/* endpoints of the Docker API proxied by Portainer bypass authorization checks. A standard (non-admin) user with access to a Docker environment can install and enable a Docker plugin, which runs as root on your host.
CVE-2026-44849 (CVSS 9.4) — The Swarm restrictions mechanism can be bypassed: a Portainer user with reduced rights can perform actions normally reserved for administrators on a Swarm cluster.
Fixed versions: 2.33.8, 2.39.2, 2.41.0. Further critical flaws have been published since, fixed in 2.45.2 and 2.45.3 and with no fix on the 2.39 branch: target 2.45.3 LTS, the latest release as of 7 November 2026.
To check your version:
docker exec portainer /portainer --versionTo update:
docker pull portainer/portainer-ce:lts
docker stop portainer
docker rm portainer
docker run -d \
--name=portainer \
--restart=always \
-p 127.0.0.1:9443:9443 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
portainer/portainer-ce:ltsIf you are below 2.45.3, update before continuing to use the instance. This command publishes the interface on 127.0.0.1 only: reach it through an SSH tunnel (ssh -L 9443:127.0.0.1:9443 user@your-vps, then https://localhost:9443) or a reverse proxy. Add -p 8000:8000 only if you use Edge agents. Portainer exposed on the internet without patches is a direct intrusion vector into your Docker host.
Dokploy CVE: the hardcoded secret (< 0.29.3)
Dokploy is not without vulnerabilities. CVE-2026-45631 (CVSS 10.0) affected default installations prior to 0.29.3: when the BETTER_AUTH_SECRET variable was not set — and the install script did not set it — the code fell back to a hardcoded value, identical on every instance. Anyone could forge a token and take over the administrator account, without authentication.
The fix landed in 0.29.3, together with a migration script to run after updating. Many other critical flaws (command injections, missing access controls) were fixed up to 0.29.13: do not stay below it, and target the latest release (0.31.0 as of 7 November 2026). To check and update:
# Check the installed version (service image tag)
docker service inspect dokploy --format '{{.Spec.TaskTemplate.ContainerSpec.Image}}'
# Update via the official script
curl -sSL https://dokploy.com/install.sh | sh -s updateSince 0.29.3, Dokploy reads BETTER_AUTH_SECRET from a Docker secret (BETTER_AUTH_SECRET_FILE variable). After updating, check that the service uses it:
docker service inspect dokploy --format '{{range .Spec.TaskTemplate.ContainerSpec.Env}}{{println .}}{{end}}' | grep BETTER_AUTH_SECRET_FILEIf the line is missing, run the official security script published with 0.29.3: it generates a unique secret and updates the service.
curl -sSL https://dokploy.com/security/0.29.3.sh | bashPrerequisites to host Portainer or Dokploy
Both tools run on Docker. Dokploy requires slightly more resources as it bundles a PostgreSQL database and a Traefik proxy alongside the main service.
Minimum recommended resources
- Portainer 2.x: the vendor publishes no hardware minimum; 2.45.3 is validated with Docker 28.5.1 and 29.8.1 — runs on a small VPS
- Dokploy: at least 2 GB RAM and 30 GB disk according to the official documentation — PostgreSQL and Traefik run alongside the main service
- A domain or subdomain pointing to your VPS (Dokploy needs it for automatic certificates)
- Ports 80 and 443 open if you use Let's Encrypt via Traefik (Dokploy)
- Root or sudo SSH access for initial installation
Deploy Dokploy on a VPS (≥ 0.29.13)
Install via the official script
The simplest and most up-to-date method:
curl -sSL https://dokploy.com/install.sh | shThe script installs Docker if needed, initialises Docker Swarm, creates the
dokployanddokploy-postgresservices plus thedokploy-traefikcontainer (configuration in/etc/dokploy/), generates a randomBETTER_AUTH_SECRETstored in a Docker secret and starts everything.Verify the BETTER_AUTH_SECRET
After installation, make sure the service reads its secret:
docker service inspect dokploy --format '{{range .Spec.TaskTemplate.ContainerSpec.Env}}{{println .}}{{end}}' | grep BETTER_AUTH_SECRET_FILEYou should see a
BETTER_AUTH_SECRET_FILE=/run/secrets/...line: the value (64 hex characters generated byopenssl rand -hex 32) stays in the Docker secret and never appears in clear text. If the line is missing (older installation), run the official security script:curl -sSL https://dokploy.com/security/0.29.3.sh | bashAccess the interface and create the admin account
Dokploy listens on port 3000 by default. Open
http://<your-vps-ip>:3000in your browser. The first visit prompts you to create the administrator account. Do this immediately — the interface is public until this account exists.Configure your domain and enable HTTPS
In Settings → Web Server → Server Domain, enter your FQDN (
dokploy.yourdomain.com). Traefik automatically retrieves a Let's Encrypt certificate via ACME HTTP-01. Your DNS must point to the VPS before this step. Once the interface is served over HTTPS, close port 3000 to the internet, as Dokploy's hardening guide recommends:ufwalone does not filter Docker-published ports, so useufw-dockeror your provider's firewall.Update Dokploy
Dokploy exposes an update button in Settings → Web Server, or use the script:
curl -sSL https://dokploy.com/install.sh | sh -s updateWith the
updateargument, the script pulls the latest release image and updates thedokployservice without overwriting your configuration or projects. Without it, the script runs a full installation, which fails on a server where Dokploy is already installed (ports 80, 443 and 3000 in use).
Portainer as an alternative — quick installation
If you prefer to stay on Portainer (existing instance or deliberate choice), the reference command:
docker run -d \
--name=portainer \
--restart=always \
-p 127.0.0.1:9443:9443 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
portainer/portainer-ce:ltsAccess through an SSH tunnel (ssh -L 9443:127.0.0.1:9443 user@your-vps, then https://localhost:9443) or a reverse proxy. Replace lts with 2.45.3 to pin the version and avoid silent updates.
Portainer 2.x vs Dokploy — full comparison 2026
Scroll the table
| Criterion | Portainer 2.x | Dokploy |
|---|---|---|
| Licence | CE free (zlib licence) | Apache 2.0 (except /proprietary folder) |
| CE future | CE stops at 2.45 (fixes until August 2027) | Active roadmap, growing community |
| Active CVEs | CVE-2026-44848 + 44849 (CVSS 9.4) and later advisories: target 2.45.3 | CVE-2026-45631 (CVSS 10.0) if < 0.29.3; target ≥ 0.29.13 |
| Minimum RAM | Not published (lightweight tool) | 2 GB (PostgreSQL + Traefik included) |
| Git deployment | Yes (Compose stacks from a Git repository) | Yes (GitHub, GitLab, Gitea, Bitbucket) |
| TLS certificates | Manual (external reverse proxy) | Automatic Let's Encrypt via Traefik |
| App templates | Yes (built-in library) | Yes (imported Docker Compose) |
| Private registry | Yes | Yes |
| Integrated CI/CD | GitOps updates (polling or webhook) | Git webhooks + notifications |
| Kubernetes | Yes (CE and BE) | No (Docker Compose / Swarm) |
| Multi-server | Yes (endpoints) | Yes (remote servers over SSH) |
| Interface | Mature, exhaustive | Modern, project-oriented |
When to stay on Portainer, when to move to Dokploy
Staying on Portainer 2.x makes sense in three situations: you already manage a Docker Swarm or Kubernetes cluster with complex configurations; you have teams that know the interface well with documented runbooks; or you need the level of detail on networks and volumes that Portainer exposes natively.
In these cases, the non-negotiable requirement is being on an up-to-date version (2.45.3 as of 7 November 2026) and restricting access to the interface: never expose port 9000/9443 directly on the internet, place Portainer behind a reverse proxy with additional authentication if non-admin users access it.
Migrating to Dokploy is the right decision if: you are starting fresh on a new VPS; you want developers to deploy from Git without SSH; you manage multiple domains and do not want to maintain Nginx/Traefik manually; or you are looking for an alternative after the end of CE (Portainer 3 has no Community Edition) for your Docker use case.
Migration from Portainer to Dokploy: concrete steps
Export your Portainer inventory
Before migrating, list your active stacks and containers:
docker ps --format 'table {{.Names}} {{.Image}} {{.Status}}' docker volume ls docker network ls --filter driver=bridgeFor each Portainer stack, retrieve the
docker-compose.ymlfrom the interface (Stacks → your stack → Editor) or from the filesystem.Back up data volumes
Named volumes contain your application data. Back them up before any operation:
docker run --rm \ -v <volume_name>:/data \ -v $(pwd):/backup \ alpine tar czf /backup/<volume_name>.tar.gz /dataRepeat for each critical volume.
Install Dokploy on the same VPS or a dedicated VPS
If migrating on the same host, stop Portainer first to free the ports:
docker stop portainer && docker rm portainerThen run the Dokploy installation (see procedure above). Dokploy uses Traefik on ports 80/443 — verify no other service occupies them.
Recreate your projects in Dokploy
In Dokploy, create a Compose Project for each Portainer stack:
1. Projects → New project
2. Add a Docker Compose type service
3. Paste yourdocker-compose.ymlor point to your Git repository
4. Configure environment variables in the Environment tab
5. Assign a domain — Dokploy configures Traefik automaticallyVerify and switch DNS
Deploy first on a test subdomain (
test.yourdomain.com). Once validated, update your DNS entries to point to Dokploy. Traefik issues certificates automatically once DNS propagates.
Troubleshooting: common errors
Portainer — interface unreachable after update
Verify that the portainer_data volume is properly mounted. An update without a named volume starts from an empty base and prompts for admin account creation again.
Portainer — TLS handshake error on port 9443
Portainer generates a self-signed certificate. Your browser warns you correctly. Place Portainer behind a reverse proxy (nginx, Caddy) with a real certificate for secure production access.
Dokploy — Traefik not generating a certificate
Most common cause: DNS has not yet propagated to the VPS. Check with dig +short your-domain.com. Traefik waits for a valid DNS response before starting the ACME challenge. If DNS is correct, verify port 80 is open (ufw allow 80).
Dokploy — Git deployment failing silently
Check the service logs in the interface (Deployments → Logs tab). On a private repository, a frequent cause is a missing SSH key, or one not authorised on the repository. Dokploy lets you create one in Settings → SSH Keys (Create SSH Key button), to add as a deploy key on the repository; for GitHub, GitLab, Gitea or Bitbucket, you can also connect the provider in Dokploy's Git settings.
The two tools are not always mutually exclusive
Portainer can manage multiple remote Docker endpoints from a single interface — including a VPS where Dokploy is installed. Some teams keep Portainer as an observation tool and Dokploy as a deployment tool. This is not an antipattern if roles are clearly separated. The cost: two surfaces to patch. If you have chosen this architecture, make sure both tools are up to date — see the CVEs at the start of the article.
To go further: alternatives to Portainer CE, complete Dokploy deployment guide and the critical Dokploy CVE 2026 update.