Files
2025-11-29 17:56:41 +08:00
..
2025-11-29 17:56:41 +08:00
2025-11-29 17:56:41 +08:00

Name: hetzner-provisioner Type: Infrastructure / DevOps Model: Claude Sonnet 4.5 (balanced for IaC generation) Status: Planned


Overview

Automated Hetzner Cloud infrastructure provisioning using Terraform or Pulumi. Generates production-ready IaC code for deploying SaaS applications at $10-15/month instead of $50-100/month on Vercel/AWS.

When This Skill Activates

Keywords: deploy on Hetzner, Hetzner Cloud, budget deployment, cheap hosting, $10/month, cost-effective infrastructure

Example prompts:

  • "Deploy my NextJS app on Hetzner"
  • "I want the cheapest possible hosting for my SaaS"
  • "Set up infrastructure on Hetzner Cloud with Postgres"
  • "Deploy for under $15/month"

What It Generates

1. Terraform Configuration

main.tf:

terraform {
  required_providers {
    hcloud = {
      source = "hetznercloud/hcloud"
      version = "~> 1.45"
    }
  }
}

provider "hcloud" {
  token = var.hcloud_token
}

# Server instance
resource "hcloud_server" "app" {
  name        = "my-saas-app"
  server_type = "cx11"
  image       = "ubuntu-22.04"
  location    = "nbg1"  # Nuremberg, Germany

  user_data = file("${path.module}/cloud-init.yaml")

  public_net {
    ipv4_enabled = true
    ipv6_enabled = true
  }
}

# Managed Postgres database
resource "hcloud_database" "postgres" {
  name              = "my-saas-db"
  engine            = "postgresql"
  version           = "15"
  size              = "db-1x-small"
  location          = "nbg1"
}

# Firewall
resource "hcloud_firewall" "app" {
  name = "my-saas-firewall"

  rule {
    direction = "in"
    protocol  = "tcp"
    port      = "80"
    source_ips = ["0.0.0.0/0", "::/0"]
  }

  rule {
    direction = "in"
    protocol  = "tcp"
    port      = "443"
    source_ips = ["0.0.0.0/0", "::/0"]
  }

  rule {
    direction = "in"
    protocol  = "tcp"
    port      = "22"
    source_ips = ["0.0.0.0/0", "::/0"]  # Restrict to your IP in production
  }
}

# Apply firewall to server
resource "hcloud_firewall_attachment" "app" {
  firewall_id = hcloud_firewall.app.id
  server_ids  = [hcloud_server.app.id]
}

# Output deployment info
output "server_ip" {
  value = hcloud_server.app.ipv4_address
}

output "database_host" {
  value = hcloud_database.postgres.host
}

output "database_port" {
  value = hcloud_database.postgres.port
}

2. Docker Configuration

Dockerfile:

FROM node:20-alpine AS base

# Dependencies
FROM base AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

# Builder
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

# Runner
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

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

3. GitHub Actions CI/CD

.github/workflows/deploy.yml:

name: Deploy to Hetzner

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3

      - name: Terraform Init
        run: terraform init
        working-directory: ./terraform
        env:
          HCLOUD_TOKEN: ${{ secrets.HETZNER_API_TOKEN }}

      - name: Terraform Plan
        run: terraform plan
        working-directory: ./terraform
        env:
          HCLOUD_TOKEN: ${{ secrets.HETZNER_API_TOKEN }}

      - name: Terraform Apply
        run: terraform apply -auto-approve
        working-directory: ./terraform
        env:
          HCLOUD_TOKEN: ${{ secrets.HETZNER_API_TOKEN }}

      - name: Build and Deploy Docker
        run: |
          ssh ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }} << 'EOF'
            cd /app
            git pull
            docker-compose build
            docker-compose up -d
          EOF

4. SSL Configuration (Let's Encrypt)

nginx.conf (auto-generated):

server {
    listen 80;
    server_name your-domain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name your-domain.com;

    ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

Cost Breakdown

Small SaaS (100-1000 users)

  • CX11 (1 vCPU, 2GB RAM): $5.83/month
  • Managed Postgres (2GB): $5.00/month
  • Storage (20GB): $0.50/month
  • SSL (Let's Encrypt): Free
  • Total: ~$11.33/month

Medium SaaS (1000-10000 users)

  • CX21 (2 vCPU, 4GB RAM): $6.90/month
  • Managed Postgres (4GB): $10.00/month
  • Storage (40GB): $1.00/month
  • Total: ~$18/month

Large SaaS (10000+ users)

  • CX31 (2 vCPU, 8GB RAM): $14.28/month
  • Managed Postgres (8GB): $20.00/month
  • Storage (80GB): $2.00/month
  • Total: ~$36/month

Test Cases

Test 1: Basic Provision

File: test-cases/test-1-basic-provision.yaml Scenario: Provision CX11 instance with Docker Expected: Terraform code generated, cost ~$6/month

Test 2: Postgres Provision

File: test-cases/test-2-postgres-provision.yaml Scenario: Add managed Postgres database Expected: Database resource added, cost ~$11/month

Test 3: SSL Configuration

File: test-cases/test-3-ssl-config.yaml Scenario: Configure SSL with Let's Encrypt Expected: Nginx + Certbot configuration, HTTPS working

Verification Steps

See test-results/README.md for:

  1. How to run each test case
  2. Expected vs actual output
  3. Manual verification steps
  4. Screenshots of successful deployment

Integration with Other Skills

  • cost-optimizer: Recommends Hetzner when budget <$20/month
  • devops-agent: Provides strategic infrastructure planning
  • nextjs-agent: NextJS-specific deployment configuration
  • nodejs-backend: Node.js app deployment
  • monitoring-setup: Adds Uptime Kuma monitoring

Limitations

  • EU-only: Data centers in Germany/Finland (GDPR-friendly but not global)
  • No auto-scaling: Manual scaling only (upgrade instance type)
  • Single-region: Multi-region requires manual setup
  • No serverless: Traditional VM-based hosting

Alternatives

When NOT to use Hetzner:

  • Global audience: Use Vercel (global edge network)
  • Auto-scaling needed: Use AWS/GCP
  • Serverless preferred: Use Vercel/Netlify
  • Enterprise SLA required: Use AWS/Azure with support plans

Future Enhancements

  • Kubernetes (k3s) cluster setup
  • Load balancer configuration
  • Multi-region deployment
  • Auto-scaling with Hetzner Cloud API
  • Monitoring integration (Grafana + Prometheus)
  • Disaster recovery automation

Status: Planned (Increment 003) Priority: P1 Tests: 3+ test cases required Documentation: .specweave/docs/guides/hetzner-deployment.md