Deploying Docker on Hetzner

Posted on by Martin Novák · #docker #hetzner #deployment

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_dump and pushes the result to an S3‑compatible bucket hosted on MinIO in the same LAN.