The fastest way to stop “it works on my machine” chaos is a self host docker guide you can repeat on any VPS in under an hour. Self-hosting Docker gives you predictable deploys, clean isolation, and control over cost—without buying into a full platform. This post is the opinionated, battle-tested path: secure a VPS, install Docker, run a reverse proxy, and ship apps with minimal drama.
1) Pick a VPS: boring choices win
Your VPS is your reliability ceiling. For most hobby-to-small-prod workloads, prioritize CPU performance, fast storage, and a data center near your users.
Practical picks:
- hetzner: excellent price/performance in EU. Great for always-on side projects and small SaaS.
- digitalocean: smooth UX, predictable networking, good docs. Nice when you value time over pennies.
- Other solid options: linode, vultr.
Sizing rule of thumb:
- 1 vCPU / 1–2 GB RAM: reverse proxy + 1 small service.
- 2 vCPU / 4 GB RAM: a couple containers + database (light traffic).
- Add RAM before CPU if you run Postgres/Redis.
Also: set up DNS early. If you use cloudflare for DNS, you get easy records management and decent protection knobs later.
2) Secure the box before you touch Docker
Docker won’t save you from a wide-open server.
Do this first:
- Use SSH keys (disable password auth).
- Create a non-root user with sudo.
- Enable a firewall (UFW) and only open what you need.
Minimum ports for a typical web stack:
- 22/tcp (SSH)
- 80/tcp (HTTP) and 443/tcp (HTTPS)
If you’re behind Cloudflare and only serving web traffic, you can later restrict inbound 80/443 to Cloudflare IP ranges—but don’t start there. Get it working first, then tighten.
3) Install Docker + Compose (the sane way)
Use the official Docker repo rather than your distro’s ancient package.
A quick Ubuntu/Debian approach:
- Install Docker Engine from Docker’s official instructions.
- Install the Compose plugin.
- Add your user to the
dockergroup (or keep usingsudo dockerif you’re strict about privilege).
Why I prefer the plugin: it’s the current path, avoids random docker-compose binary mismatches, and plays nicely with upgrades.
Operational advice that saves pain:
- Keep your app configs in
/opt/<project>. - Use
.envfiles for secrets only if permissions are locked down. Better: Docker secrets or a dedicated secrets manager when you grow. - Set log rotation (Docker defaults can eat disk).
4) Run a reverse proxy + TLS with Compose (actionable example)
You can manually expose ports for each app, but that becomes a mess. A reverse proxy gives you:
- One public entrypoint (80/443)
- Multiple apps on subdomains
- Automatic TLS certificates
Below is a minimal Caddy setup with Docker Compose. Caddy is opinionated and low-maintenance—perfect for self-hosting.
services:
caddy:
image: caddy:2
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
app:
image: ghcr.io/yourorg/yourapp:latest
container_name: app
restart: unless-stopped
environment:
- NODE_ENV=production
volumes:
caddy_data:
caddy_config:
Example Caddyfile:
app.example.com {
reverse_proxy app:3000
}
Notes:
- Point
app.example.comto your VPS IP (via Cloudflare or any DNS provider). - Your
appcontainer must listen on:3000internally. - Caddy will obtain and renew TLS certs automatically.
This pattern scales: add more services, add more hostnames, keep the public surface small.
5) Ship and maintain like you mean it (soft recommendations)
Self-hosting isn’t “set and forget.” It’s “set and maintain.” The good news: you only need a few habits.
What I’d standardize:
- Updates: patch the OS monthly, rebuild containers when base images update.
- Backups: snapshot the VPS and back up databases separately. Practice a restore once.
- Observability: at minimum, container healthchecks + uptime monitoring. Logs should be searchable before an incident.
- Resource limits: set CPU/memory limits for noisy neighbors (even if the neighbor is your own cron job).
When to consider switching providers (gently):
- If you want a simpler UI and managed extras while keeping control, digitalocean is comfortable.
- If cost efficiency matters and you’re fine with a slightly more hands-on vibe, hetzner is hard to beat.
- If you’re using cloudflare DNS already, you can later layer in caching and basic WAF rules without redesigning your stack.
The point: pick a boring VPS, lock it down, run a reverse proxy, and keep your Docker setup repeatable. That’s the difference between “a server” and “an environment you can trust.”
Some links in this article are affiliate links. We may earn a commission at no extra cost to you if you make a purchase through them.
Top comments (0)