How to Secure a VPS That Runs Docker Containers
SSH hardening, a default-deny firewall, and the fix for the problem that catches almost everyone — Docker publishing container ports straight past UFW into the public internet.
Take a blank Ubuntu VPS to a server that runs containerised applications safely: firewalls that cover Docker, a real deployment, restorable backups, and a pipeline that ships changes.
This path assumes you can use a shell and nothing else. It ends with a server that runs real workloads, is backed up, and is deployed to from CI rather than from your laptop.
The order matters more than usual here. The firewall article comes first because the Docker port-publishing behaviour it covers invalidates the mental model most people bring from non-container hosts — and every later article assumes you know about it.
Budget four hours in total, spread over a few sessions. The two long ones are the deployment article and the backup article, and the backup article is the one people skip and regret.
Read in order. Ticking a lesson stores your progress in this browser only.
SSH hardening, a default-deny firewall, and the fix for the problem that catches almost everyone — Docker publishing container ports straight past UFW into the public internet.
Start here. The firewall behaviour covered in this article affects every later step.
Create the A record, pick the right proxy status, set the SSL mode to Full (strict) and lock the origin so only Cloudflare can reach it — plus what errors 521, 522, 525 and 526 actually mean.
DNS, proxy status and TLS termination before you deploy anything real.
A production n8n install on Ubuntu 24.04 using Docker Compose, PostgreSQL and Caddy for automatic TLS — with the encryption key, webhook URL and backup steps most quickstarts leave out.
A three-service Compose stack with a database and automatic certificates.
A nightly backup for a self-hosted Docker host: database dumps taken with the right tools, file volumes snapshotted with restic, a systemd timer to run it, and a restore test that proves it works.
Database dumps and volume snapshots on a systemd timer, with a restore test.
Diagnose which file descriptor limit you actually hit — container, daemon or kernel — then raise it properly, and work out whether the real problem is a leak rather than a limit.
The first operational failure most self-hosted stacks hit.
Both put a Heroku-style deploy UI on your own VPS, but on different container primitives. What that means for zero-downtime deploys, multi-server growth and the day the control plane breaks.
Optional. Read it when one Compose file has become five.
Dokploy runs apps as Docker Swarm services, so rolling updates are already there — they just need a real health check and an update policy. Plus how to prove no request was dropped.
Health checks and rolling update policy.
Test, build a container image, push it to GHCR and deploy it to a VPS over SSH — with the concurrency guard, environment approval and token scoping that keep the pipeline from becoming the weak point.
Build once in CI, deploy the exact image you tested.
SSH hardening, a default-deny firewall, and the fix for the problem that catches almost everyone — Docker publishing container ports straight past UFW into the public internet.
Create the A record, pick the right proxy status, set the SSL mode to Full (strict) and lock the origin so only Cloudflare can reach it — plus what errors 521, 522, 525 and 526 actually mean.
A production n8n install on Ubuntu 24.04 using Docker Compose, PostgreSQL and Caddy for automatic TLS — with the encryption key, webhook URL and backup steps most quickstarts leave out.
A nightly backup for a self-hosted Docker host: database dumps taken with the right tools, file volumes snapshotted with restic, a systemd timer to run it, and a restore test that proves it works.
Diagnose which file descriptor limit you actually hit — container, daemon or kernel — then raise it properly, and work out whether the real problem is a leak rather than a limit.
Both put a Heroku-style deploy UI on your own VPS, but on different container primitives. What that means for zero-downtime deploys, multi-server growth and the day the control plane breaks.
Dokploy runs apps as Docker Swarm services, so rolling updates are already there — they just need a real health check and an update policy. Plus how to prove no request was dropped.
Test, build a container image, push it to GHCR and deploy it to a VPS over SSH — with the concurrency guard, environment approval and token scoping that keep the pipeline from becoming the weak point.