Produkce na Hetzneru: Reverzní proxy Caddy, sítě web/db, rozcestník a deploy.sh
Detailní pohled na reálnou produkční architekturu na Hetzner serveru debian-hell (65.109.132.79): reverzní proxy Caddy s automatickým HTTPS, sdílená síť pro PostgreSQL 18 a atomický deployment přes deploy.sh.
Reálná produkční infrastruktura na serveru Hetzner
Naše výuková platforma běží na produkčním serveru Hetzner pod hostitelským jménem debian-hell (IP 65.109.132.79). Server provozuje více nezávislých služeb sdružených pod centrálním rozcestníkem https://heatsharing.cz.
Celá architektura je navržena podle principů striktní izolace a minimálních privilegií:
┌──────────────────────────────────────────────┐
│ Internet │
└──────────────────────┬───────────────────────┘
│ HTTPS (443)
▼
┌──────────────────────────────────────────────┐
│ Centrální Caddy Reverzní Proxy │
│ (/srv/caddy — Automatické SSL) │
└──────────────┬───────────────────────────────┘
│
Docker Network: │ web (Bridge)
┌────────────────────────┴────────────────────────┐
▼ ▼
┌──────────────────────────────┐ ┌──────────────────────────────┐
│ Aplikace agy-laravel │ │ Další aplikace na serveru │
│ (laravel.heatsharing.cz) │ │ (spytihnev.heatsharing.cz) │
│ FrankenPHP :80 │ │ (/srv/apps/obecni-web) │
└──────────────┬───────────────┘ └──────────────┬───────────────┘
│ │
└──────────────┐ ┌──────────────┘
Docker Network:│ db │
▼ ▼
┌────────────────────────────────┐
│ Sdílený PostgreSQL 18 │
│ (/srv/postgres) │
└────────────────────────────────┘
Proč využíváme externí Docker sítě web a db?
V souboru compose.prod.yaml jsou obě sítě deklarovány s příznakem external: true:
networks:
db:
external: true
web:
external: true
Tento vzor přináší zásadní výhody:
- Žádné vystavené porty do internetu: Kontejner
agy-laravelnemá v produkci definovanou sekciports:. Není přímo přístupný z veřejného internetu na žádném portu. - Směrování přes Caddy: Reverzní proxy Caddy v
/srv/caddyposlouchá na veřejných portech 80/443. Když přijde požadavek na hostitelelaravel.heatsharing.cz, Caddy jej přes interní Docker DNS sítěwebpřepošle na kontejner s aliasemagy-laravel:80. Caddy se také automaticky stará o vystavení a obnovu Let's Encrypt certifikátů. - Sdílená a izolovaná databáze: Sdílený PostgreSQL 18 kontejner běží v
/srv/postgresna sítidb. Naše aplikace s ním komunikuje po privátní síti pod uživatelem a databázíagy_laravel, aniž by měla přístup k databázím jiných aplikací na serveru.
Nasazení pomocí skriptu deploy.sh
Proces aktualizace aplikace v produkci je plně automatizován v kořenovém skriptu deploy.sh:
#!/bin/sh
set -e
echo "==> Building production Docker image..."
docker compose -f compose.prod.yaml build app
echo "==> Deploying application..."
docker compose -f compose.prod.yaml up -d --wait
echo "==> Deployment complete. Service status:"
docker compose -f compose.prod.yaml ps
Kouzlo parametru --wait a Healthchecku:
Příkaz docker compose up -d --wait je klíčový. Bez parametru --wait by Docker kontejner spustil na pozadí a okamžitě ohlásil hotovo, aniž by tušil, zda aplikace skutečně naběhla.
S přepínačem --wait Docker sleduje náš definovaný Healthcheck:
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost/up >/dev/null || exit 1"]
interval: 30s
timeout: 5s
start_period: 60s
retries: 3
Kontejner provolává náš Laravel endpoint /up. Teprve ve chvíli, kdy:
entrypoint.shúspěšně počkal na databázi,- proběhly databázové migrace a seedy,
- zahřála se cache přes
optimize, - a FrankenPHP začal odpovídat kódem
200 OKna/up,
Docker označí kontejner za healthy a starý kontejner bezpečně přepojí na nový bez jediného výpadku pro uživatele (Zero-Downtime Deployment).
# compose.prod.yaml — Reálná produkční konfigurace
name: agylaravel
services:
app:
image: agylaravel/course:prod
build:
context: .
dockerfile: docker/Dockerfile
target: prod
env_file: .env
restart: unless-stopped
environment:
PHP_OPCACHE_VALIDATE_TIMESTAMPS: "0"
RUN_MIGRATIONS: "1"
RUN_SEEDERS: "1"
volumes:
- storage:/app/storage
networks:
db: {}
web:
aliases:
- agy-laravel
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost/up >/dev/null || exit 1"]
interval: 30s
timeout: 5s
start_period: 60s
retries: 3
networks:
db:
external: true
web:
external: true
volumes:
storage:
🎯 Ověření znalostí z lekce
Kvíz otestuje pochopení architektury a reálného kódu probíraného v této lekci.
Jak je na našem Hetzner serveru zajištěno automatické vystavení a obnova SSL/TLS certifikátů pro laravel.heatsharing.cz?
Proč má služba app v souboru compose.prod.yaml připojení do dvou externích sítí web a db?
Jakou roli hraje přepínač --wait v příkazu docker compose -f compose.prod.yaml up -d --wait ve skriptu deploy.sh?
| 1 | name: agylaravel |
| 2 | |
| 3 | services: |
| 4 | app: |
| 5 | image: agylaravel/course:prod |
| 6 | build: |
| 7 | context: . |
| 8 | dockerfile: docker/Dockerfile |
| 9 | target: prod |
| 10 | env_file: .env |
| 11 | restart: unless-stopped |
| 12 | environment: |
| 13 | PHP_OPCACHE_VALIDATE_TIMESTAMPS: "0" |
| 14 | RUN_MIGRATIONS: "1" |
| 15 | RUN_SEEDERS: "1" |
| 16 | volumes: |
| 17 | - storage:/app/storage |
| 18 | networks: |
| 19 | db: {} |
| 20 | web: |
| 21 | aliases: |
| 22 | - agy-laravel |
| 23 | healthcheck: |
| 24 | test: ["CMD-SHELL", "curl -fsS http://localhost/up >/dev/null || exit 1"] |
| 25 | interval: 30s |
| 26 | timeout: 5s |
| 27 | start_period: 60s |
| 28 | retries: 3 |
| 29 | |
| 30 | networks: |
| 31 | db: |
| 32 | external: true |
| 33 | web: |
| 34 | external: true |
| 35 | |
| 36 | volumes: |
| 37 | storage: |
| 38 | |