Deploying Docker on Hetzner
Hetzner offers rock-solid cloud instances at a fair price. In this guide I’ll walk you through my standard Docker deployment flow on a fresh Hetzner VPS running Ubuntu 22.04.
1. Initial server setup
After you provision a CX21 instance via the Hetzner Cloud Console, SSH in as root:
ssh root@your-server-ip
Update packages and create a non‑root user with sudo:
apt update && apt upgrade -y
useradd -m -G sudo deploy
passwd deploy
2. Install Docker
We use the official Docker repository for the latest stable version:
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
usermod -aG docker deploy
Log out and back in as deploy, then verify:
docker --version
docker run hello-world
3. Configuring the application
I keep my Docker Compose files in /opt/app. Example docker-compose.yml for a typical Node.js + PostgreSQL stack:
version: '3.8'
services:
web:
image: registry.internal.webprague.com/app:latest
ports:
- "127.0.0.1:3000:3000"
env_file: .env.production
depends_on:
- db
restart: always
db:
image: postgres:16-alpine
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
restart: always
volumes:
pgdata:
4. Reverse proxy with Caddy
Caddy handles SSL termination and forwards traffic to the Docker host:
# /etc/caddy/Caddyfile
www.webprague.com {
reverse_proxy 127.0.0.1:3000
tls internal
}
Make sure Caddy is running as a systemd unit.
5. Deployment automation
I use a simple Git‑based workflow on a private GitLab instance. When a push hits main, a runner on the Hetzner host pulls the image and runs:
docker compose pull
docker compose up -d
For database migrations we add a one‑shot job in the pipeline.
6. Monitoring
Prometheus + Node Exporter run as Docker containers; metrics are scraped every 15s and displayed on a local Grafana dashboard at monitor.internal.webprague.com:3001.
That’s the core setup. Questions? Leave a comment below!
Comments
Anna –
Nice writeup! Do you also handle backups of the PostgreSQL volume?
Martin –
I run a cron job that does
pg_dumpand pushes the result to an S3‑compatible bucket hosted on MinIO in the same LAN.