Deploy Docker to VPS servers with health checks and zero downtime swaps
When your app already ships as a container, the server should not care which language is inside. Deployment installs Docker Engine on your own VPS, builds from your Dockerfile or pulls from your registry, runs the stack from your compose file, and swaps containers only after the new one reports healthy. Domains, SSL, logs, backups and rollback work exactly as they do for every other stack.
What Deployment detects in a Docker repository
A Dockerfile at the root (or the path you choose) marks a container build. A compose.yaml or docker-compose.yml describes the whole stack: Deployment reads its services, figures out which one receives web traffic from the published or exposed port, and treats the others (workers, schedulers, sidecars) as background services. HEALTHCHECK instructions and compose healthcheck blocks are used for the switch. Database and Redis services in compose are flagged, so you can decide whether to keep them in containers or move them to managed instances on the host.
- Single Dockerfile apps and multi-service compose stacks
- Build on the server, or pull a prebuilt image from GHCR, Docker Hub, ECR or any registry
- Web service and port detected from the compose file
- Health checks from the image or compose definition
What Deployment installs on a Docker host
Provisioning hardens the VPS (firewall, key-only SSH, unattended security updates, swap), installs Docker Engine and the compose plugin from the official repository, and sets log rotation for containers so disks do not fill up. nginx or Caddy runs on the host in front of your containers with free Let's Encrypt certificates. Containers publish only on 127.0.0.1, which avoids the common mistake of Docker opening ports straight through the firewall. Registry credentials are stored encrypted per site.
A Docker deploy script with build and migrate
If you build images in CI, replace the build line with docker compose pull for the commit tag. Deployment tags every image with the commit SHA, so each release knows exactly which image it runs, and old images are pruned when their release leaves the history.
cd "$RELEASE_PATH" docker compose -p "$SITE_SLUG" build --pull web worker docker compose -p "$SITE_SLUG" run --rm web ./bin/migrate
Health checks that decide the switch
A container that starts is not the same as a container that works. Deployment waits until Docker reports the new web container healthy, and also calls the health URL through the proxy path, before it moves any traffic.
services:
web:
build: .
expose: ["8080"]
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8080/health"]
interval: 5s
retries: 6
worker:
build: .
command: ["./bin/worker"]Zero downtime container swaps and rollback
Plain docker compose up stops the old container before the new one is ready, which means seconds of errors on every release. Deployment starts the new web container next to the old one on a different local port, waits for its health check, switches the upstream in nginx or Caddy, and then stops the old container with a grace period so open requests finish. Workers get SIGTERM and their configured stop timeout before replacement. If the health check fails, the new container is removed and nothing changes for users. Rollback starts the image of a previous release, which is still on the host, and switches back in seconds. See zero downtime deployment and deploy from git for the shared mechanics.
Databases for containerized apps
Databases in containers work, but backups and upgrades get harder. Deployment lets you run MySQL, MariaDB, PostgreSQL and Redis directly on the host or on a separate database server, with users, grants and scheduled backups to your own S3-compatible storage. Containers reach them over the Docker bridge or the private network. If you keep a database in compose, mount a named volume and add a backup job; the cron log shows every run. Volumes for uploads persist across releases.
Which plan fits your Docker deployments
| Plan | Monthly | Yearly, per month | Servers | Deploy history |
|---|---|---|---|---|
| Starter | 49 USD | 24 USD | 3 | 14 days |
| Team | 199 USD | 99 USD | 15 | 90 days |
| Agency | 499 USD | 249 USD | 60 | 1 year |
| Enterprise | 1 499 USD | 749 USD | 250 | 2 years |
A compose stack on one or two VPS fits Starter. Teams running several stacks across environments, with roles and audit log, fit Team. If you weigh this against running a self-hosted panel yourself, read coolify alternative.
Questions about Docker on a VPS
Do I need Kubernetes for zero downtime?
No. For one to a few servers, a health-checked container swap behind a reverse proxy gives you zero downtime releases with far less to operate.
Can I mix Docker and non-Docker sites on one server?
Yes. A server can run a Laravel site with PHP-FPM, a static site and a compose stack side by side, each with its own domain and deploy history.
Which registries are supported?
Any registry Docker can log in to, including GHCR, Docker Hub, GitLab Container Registry, ECR and self-hosted registries.
Can I try Deployment before I pay?
Yes. The home page has a free one-time demo: paste a public repository URL and see the detected stack, the server plan and the first lines of the deploy script. Plans start at 49 USD a month, or 24 USD a month billed yearly.
Run your containers on servers you control
Connect a VPS, point it at your repository and push.
$ git push origin main
✓ build release 20261019-142210
✓ health check 200
✓ switch current 0.4 s
$