Configuring Website Deployment on Hetzner
Hetzner is a German cloud provider with the best price-to-performance ratio in Europe. Cloud VPS is 3–5x cheaper than AWS/GCP with comparable specs. GDPR-compliant servers in the EU.
Creating Server via hcloud CLI
# Install CLI
brew install hetzner-cloud/tap/hcloud
# Create server
hcloud server create \
--name myapp-prod \
--type cpx21 \ # 3 vCPU, 4 GB RAM — ~9€/mo
--image ubuntu-22.04 \
--location nbg1 \ # Nuremberg
--ssh-key my-key \
--user-data-file cloud-init.yaml
Terraform for Hetzner
# main.tf
terraform {
required_providers {
hcloud = {
source = "hetznercloud/hcloud"
version = "~> 1.44"
}
}
}
provider "hcloud" {
token = var.hcloud_token
}
resource "hcloud_server" "app" {
name = "myapp-prod"
image = "ubuntu-22.04"
server_type = "cpx21"
location = "nbg1"
ssh_keys = [hcloud_ssh_key.default.id]
user_data = file("cloud-init.yaml")
labels = {
env = "production"
app = "myapp"
}
}
resource "hcloud_firewall" "app" {
name = "myapp-firewall"
rule {
direction = "in"
protocol = "tcp"
port = "22"
source_ips = ["10.0.0.0/8"] # only via VPN
}
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"]
}
}
resource "hcloud_load_balancer" "lb" {
name = "myapp-lb"
load_balancer_type = "lb11"
location = "nbg1"
}
resource "hcloud_load_balancer_target" "server" {
type = "server"
load_balancer_id = hcloud_load_balancer.lb.id
server_id = hcloud_server.app.id
}
GitHub Actions Deploy
- name: Deploy to Hetzner
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.HETZNER_IP }}
username: deploy
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
set -e
cd /var/www/myapp
# Fetch changes
git fetch origin main
git reset --hard origin/main
# Update dependencies
composer install --no-dev --optimize-autoloader
npm ci --omit=dev
npm run build
# Migrations and cache
php artisan migrate --force
php artisan optimize
# Reload
sudo systemctl reload php8.3-fpm nginx
php artisan queue:restart
Hetzner Object Storage (S3-compatible)
# Setup AWS CLI for Hetzner
aws configure set aws_access_key_id $HETZNER_S3_KEY
aws configure set aws_secret_access_key $HETZNER_S3_SECRET
aws configure set region eu-central
# Create bucket
aws --endpoint-url https://fsn1.your-objectstorage.com \
s3 mb s3://myapp-assets
# Upload static files
aws --endpoint-url https://fsn1.your-objectstorage.com \
s3 sync ./dist/assets s3://myapp-assets/assets \
--cache-control "public, max-age=31536000, immutable"
Hetzner + Docker Swarm (Multiple Servers)
# Initialize Swarm on first server
ssh server1 "docker swarm init"
# Add worker nodes
JOIN_TOKEN=$(ssh server1 "docker swarm join-token worker -q")
ssh server2 "docker swarm join --token $JOIN_TOKEN server1:2377"
ssh server3 "docker swarm join --token $JOIN_TOKEN server1:2377"
# Deploy stack
docker -H ssh://deploy@server1 stack deploy \
-c docker-compose.prod.yml \
myapp
Implementation Timeline
- Single VPS + Nginx + deploy: 1–2 days
- Terraform + Load Balancer: 3–4 days
- Docker Swarm cluster: 4–5 days







