Saltar al contenido principal
Logo huiro.dev
Blog
nextjsdockerhetznertraefikdeploymenten

Deploy Next.js on Hetzner VPS with Docker and Traefik for €5/month

Complete guide to deploying Next.js apps on a €5/month Hetzner VPS using Docker Compose and Traefik for automatic SSL. Real configs from huiro.dev production setup.

Mauricio Cifuentes

12 de abril de 20265 min
Deploy Next.js on Hetzner VPS with Docker and Traefik for €5/month

I spent months burning money on Vercel and AWS before realizing I could run my entire stack on a single Hetzner VPS for €5/month. After moving huiro.dev and three client projects to this setup, I'm saving over €200/month while gaining full control over my infrastructure.

The Problem

Vercel's pricing hits hard once you scale beyond hobby projects. My main SaaS was costing €80/month just for hosting, plus another €40 for the database. Every client project added another €20-30/month. The serverless convenience wasn't worth €160/month when a VPS could handle the same load for €5.

But moving from serverless to VPS means handling SSL certificates, reverse proxies, container orchestration, and deployment pipelines yourself. I needed a setup that was both cost-effective and maintainable.

The Architecture

The solution uses Traefik as a reverse proxy with automatic SSL certificate generation, Docker Compose for container orchestration, and GitHub Actions for automated deployments. Everything runs on a single Hetzner VPS.

Next.js Deployment Architecture

Cargando diagrama...

Traefik handles incoming requests, automatically provisions SSL certificates via Let's Encrypt, and routes traffic to the appropriate Docker containers. Each Next.js app runs in its own container, making it easy to deploy multiple projects on the same server.

Step by Step

Set up the Hetzner VPS

First, create a CX11 instance (1 vCPU, 4GB RAM, €4.15/month) on Hetzner Cloud. Choose Ubuntu 22.04 LTS and add your SSH key during setup.

# SSH into your server
ssh root@your-server-ip

# Update system packages
apt update && apt upgrade -y

# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh

# Install Docker Compose
apt install docker-compose-plugin -y

# Create project directories
mkdir -p /opt/apps/huiro-blog
cd /opt/apps/huiro-blog

Configure Traefik

Create the Traefik configuration that handles SSL certificates and routing:

# docker-compose.yml
version: '3.8'

services:
  traefik:
    image: traefik:v3.0
    container_name: traefik
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/traefik.yml:ro
      - ./acme.json:/acme.json
    networks:
      - web

  huiro-blog:
    image: ghcr.io/mauriciocifuentes/huiro-blog:latest
    container_name: huiro-blog
    restart: unless-stopped
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.huiro-blog.rule=Host(`huiro.dev`)"
      - "traefik.http.routers.huiro-blog.tls.certresolver=letsencrypt"
      - "traefik.http.services.huiro-blog.loadbalancer.server.port=3000"
    networks:
      - web

networks:
  web:
    external: true

Create Traefik Configuration

The main Traefik config handles SSL certificate generation and Docker integration:

# traefik.yml
api:
  dashboard: false

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entrypoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"

providers:
  docker:
    exposedByDefault: false

certificatesResolvers:
  letsencrypt:
    acme:
      email: your-email@domain.com
      storage: acme.json
      httpChallenge:
        entryPoint: web

Prepare SSL Certificate Storage

Traefik needs a writable file to store SSL certificates:

# Create the acme.json file with correct permissions
touch acme.json
chmod 600 acme.json

# Create the Docker network
docker network create web

Build the Next.js Docker Image

Here's the production Dockerfile I use for all Next.js apps:

# Dockerfile
FROM node:18-alpine AS base

# Install dependencies
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app

COPY package.json package-lock.json* ./
RUN npm ci --only=production

# Build the app
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

RUN npm run build

# Production image
FROM base AS runner
WORKDIR /app

ENV NODE_ENV production

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT 3000
ENV HOSTNAME "0.0.0.0"

CMD ["node", "server.js"]

Set up GitHub Actions Deployment

Automate deployments with this GitHub Actions workflow:

# .github/workflows/deploy.yml
name: Deploy to Hetzner

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Build and push Docker image
        run: |
          echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
          docker build -t ghcr.io/${{ github.repository }}:latest .
          docker push ghcr.io/${{ github.repository }}:latest

      - name: Deploy to server
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.HOST }}
          username: root
          key: ${{ secrets.PRIVATE_KEY }}
          script: |
            cd /opt/apps/huiro-blog
            docker compose pull
            docker compose up -d
            docker system prune -f

Launch the Stack

Start all services and verify everything works:

# Start Traefik and your app
docker compose up -d

# Check logs
docker compose logs -f

# Verify SSL certificate
curl -I https://yourdomain.com

Results

The cost difference is dramatic. My previous setup cost €160/month across Vercel, PlanetScale, and various serverless functions. Now I run:

  • Main blog (huiro.dev)
  • Two client SaaS applications
  • Development staging environments
  • PostgreSQL database
  • Redis cache

All for €5/month on a single Hetzner CX11 instance. The server handles 50k monthly visitors without breaking a sweat, with average response times under 200ms.

SSL certificates renew automatically, deployments take 2 minutes via GitHub Actions, and I have full control over the environment.

Lessons Learned

Start small, scale horizontally — One VPS handles way more than you think. Add more servers when you actually need them, not preemptively • Traefik simplifies everything — Automatic SSL and service discovery eliminate the biggest VPS pain points. No more nginx config hell • Docker labels are powerful — Traefik's Docker provider lets you configure routing entirely through compose labels. Zero separate config files • GitHub Container Registry is free — No need for DockerHub paid plans. GHCR gives unlimited private images for any GitHub repo • Monitor disk space closely — Docker images accumulate fast. Set up automated cleanup or your 20GB disk will fill up quickly

Takeaways

• A €5/month Hetzner VPS can replace hundreds of euros in serverless costs for most applications • Traefik + Docker Compose provides serverless-like convenience with VPS control and pricing • GitHub Actions + Container Registry creates a complete CI/CD pipeline without external services • This setup scales to multiple applications and environments on the same infrastructure

What's your biggest concern about moving from serverless to VPS hosting?

huiro.dev

¿Necesitas algo así para tu empresa?

Automatización, desarrollo a medida o infraestructura. Diagnóstico gratuito de 30 minutos.