Deploying Your First App
From git clone to a live HTTPS URL, end to end.
What we are building
We will deploy a Next.js app with a Postgres database behind Traefik with an automatic Let's Encrypt cert, on a fresh Hetzner CX22 running Ubuntu 24.04. The app could be anything — Next.js, Laravel, Django, WordPress, Rails — the shape of the deployment is identical. Docker is the great equalizer.
The compose.yml that does the whole job
Three services: the app, Postgres, and Traefik. Traefik owns ports 80 and 443. The app and the database talk to each other over the internal network. Nothing except Traefik is exposed to the internet:
services:
traefik:
image: traefik:v3
command:
- --providers.docker=true
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- [email protected]
- --certificatesresolvers.le.acme.tlschallenge=true
- --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
ports: ["80:80", "443:443"]
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- letsencrypt:/letsencrypt
app:
build: .
environment:
DATABASE_URL: postgres://app:${DB_PASSWORD}@db:5432/app
labels:
- traefik.enable=true
- traefik.http.routers.app.rule=Host(`example.com`)
- traefik.http.routers.app.tls.certresolver=le
db:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
letsencrypt:
pgdata:
git clone, .env, docker compose up
SSH into the box as your non-root user, clone the repo, create a .env file with DB_PASSWORD=$(openssl rand -hex 32), and run docker compose up -d --build. On the first boot Traefik will request a cert from Let's Encrypt and your site is live on HTTPS. If the cert fails, check that your A record points to this server — it is always DNS.
Verify the three things that matter
curl -I https://example.com should return 200 with a valid cert chain. docker compose logs -f app should show your framework's startup banner with no errors. docker compose exec db psql -U app -c "\dt" should list your tables (or nothing, if this is a fresh DB before migrations). If all three pass, you have shipped.
What to do when it breaks
First rule: read docker compose logs. Ninety percent of first-deploy failures are either a missing env var (app crashes on startup) or DNS not propagated (cert request times out). Second rule: docker compose ps tells you which container is unhealthy. Third rule: if you are rebuilding in a loop, docker compose down -v nukes the volumes and starts over. Save that for a fresh setup — it deletes your database.
Key takeaways
- One Compose file owns the entire app, DB, and proxy
- Traefik labels wire the domain and cert automatically
- First-deploy failures are usually DNS or missing env vars
- `curl -I`, `logs`, and `ps` are the three debug commands you need
Related documentation
Deploying from GitHub Repository
Deploy your own applications directly from GitHub with automatic framework detection.
Deploying Apps with Templates
Use pre-built templates to deploy databases, CMS platforms, and development tools in one click.
Managing Environment Variables
Securely manage environment variables and secrets for your applications.