Fáze požadavku: 11. FrankenPHP & Produkční běh
Modul 10 · Lekce 2

Jak funguje entrypoint.sh, čekání na databázi (db:monitor) a compose.yaml

💡 Shrnutí lekce

Prozkoumejte robustní startovací skript entrypoint.sh: přípravu oprávnění pro storage, čekání na dostupnost PostgreSQL pomocí php artisan db:monitor a automatické spouštění migrací a optimalizací.

Úloha entrypoint.sh v životním cyklu kontejneru

V kontejnerové architektuře se často zapomíná na to, co se děje mezi okamžikem spuštění kontejneru (docker run) a samotným startem webového serveru. Skript docker/entrypoint.sh je prvním kódem, který se v kontejneru provede.

Jeho úkolem je:

  1. Ověřit a nastavit Linuxová oprávnění: Zajistit, že adresáře storage/ a bootstrap/cache/ existují a webserverový uživatel www-data do nich může zapisovat logy, session a cache.
  2. Koordinovat databázové závislosti: Vyřešit notoricky známý problém souběhu (Race Condition), kdy se aplikace pokusí připojit k databázi dříve, než PostgreSQL dokončí svůj vnitřní boot.
  3. Spustit migrace a seedy: Bezpečně a idempotentně uvést databázové schéma do aktuálního stavu.
  4. Zahřát produkční cache: Provést php artisan optimize.
  5. Předat řízení webserveru: Příkazem exec "$@" nahradit skript samotným procesem FrankenPHP.

Proč nestačí depends_on v Docker Compose?

V souboru compose.yaml sice můžete uvést depends_on: postgres: condition: service_healthy, ale v produkčním prostředí často používáme externí, sdílenou databázi (viz compose.prod.yaml se sítí db: external: true). V takovém případě Docker Compose nemá žádnou možnost stav externího kontejneru sledovat!

Pokud by aplikace ihned spustila php artisan migrate, narazila by na Connection refused a kontejner by havaroval (CrashLoopBackOff).

Náš entrypoint.sh tento problém elegantně řeší vestavěným příkazem Laravelu:

until php artisan db:monitor --max=1 >/dev/null 2>&1; do
    sleep 2
done

Příkaz db:monitor provede pokus o otevření PDO spojení. Smyčka until se opakuje každé dvě sekundy, dokud PostgreSQL nezačne odpovídat. Teprve poté se bezpečně spustí migrace s přepínačem --force.


Rozdíly mezi vývojovým a produkčním Compose

Porovnejte compose.yaml a compose.prod.yaml:

Parametr compose.yaml (Dev) compose.prod.yaml (Prod)
Cíl buildu (target) dev (s Xdebugem a UID mapováním) prod (vyčištěný obraz)
Kód aplikace Bind mount .:/app (změny se projeví hned) Zabudován uvnitř image, mountuje se jen storage volume
Databáze Lokální postgres:18-alpine kontejner Sdílený PostgreSQL přes externí Docker síť db
Síť a routing Vystaven lokální port 8000:80 Žádné vystavené porty, připojeno k externí síti web
Healthcheck Spoléhá na healthcheck PostgreSQL Aktivní HTTP healthcheck curl http://localhost/up

Důležitost příkazu exec "$@"

Poslední řádek entrypoint.sh zní exec "$@". Příkaz exec v unixovém shellu nahradí aktuální proces novým programem (v našem případě frankenphp run --config /etc/frankenphp/Caddyfile).

Díky tomu se FrankenPHP stane procesem s PID 1. To je naprosto zásadní:

  • Pokud by PID 1 zůstal shell skript, signály operačního systému (např. SIGTERM při docker compose stop) by se nedostaly k FrankenPHP.
  • S exec webserver signál okamžitě zachytí, dokončí rozpracované HTTP požadavky a korektně se ukončí (Graceful Shutdown).
💻 Klíčová konstrukce z praxe
#!/bin/sh
# docker/entrypoint.sh — Startovací skript kontejneru
set -e

APP_ROOT="${APP_ROOT:-/app}"
cd "$APP_ROOT"

# Zajištění zápisových práv pro webserver (www-data)
if [ "$(id -u)" = "0" ]; then
    mkdir -p storage/framework/cache/data storage/framework/sessions \
             storage/framework/views storage/logs bootstrap/cache
    chown -R www-data:www-data storage bootstrap/cache
    chmod -R 775 storage bootstrap/cache
fi

# Pokud je vyžádáno spuštění migrací (v produkci compose.prod.yaml)
if [ "${RUN_MIGRATIONS:-0}" = "1" ]; then
    # Aktivní čekání, dokud PostgreSQL nepřijímá spojení
    until php artisan db:monitor --max=1 >/dev/null 2>&1; do
        sleep 2
    done

    php artisan migrate --force

    if [ "${RUN_SEEDERS:-0}" = "1" ]; then
        php artisan db:seed --force
    fi

    # Zkompilování produkční cache konfigurace, rout a views
    php artisan optimize
fi

# Předání řízení samotnému procesu FrankenPHP (PID 1)
exec "$@"

🎯 Ověření znalostí z lekce

Kvíz otestuje pochopení architektury a reálného kódu probíraného v této lekci.

Otázka 1 z 3

Proč v entrypoint.sh používáme smyčku until php artisan db:monitor namísto okamžitého volání migrate?

Otázka 2 z 3

Co je hlavním účelem volání php artisan optimize při startu produkčního kontejneru?

Otázka 3 z 3

Proč musí startovací skript kontejneru končit příkazem exec "$@"?

Zodpovězeno: 0 / 3
🔬 Živá anatomie v této aplikaci
3 soubory
Na co se v kódu zaměřit: V souboru docker/entrypoint.sh si všimni bloku podmíněného RUN_MIGRATIONS=1, kde smyčka until php artisan db:monitor aktivně čeká na start databáze před spuštěním migrate --force a db:seed.
cesta: docker/entrypoint.sh
35 řádků · 0.8 KB
Startovací skript kontejneru zajišťující práva storage, env soubory a spuštění Caddy/Octane.
1 #!/bin/sh
2 set -e
3
4 APP_ROOT="${APP_ROOT:-/app}"
5 PHP_OPCACHE_VALIDATE_TIMESTAMPS="${PHP_OPCACHE_VALIDATE_TIMESTAMPS:-1}"
6 export APP_ROOT
7 export PHP_OPCACHE_VALIDATE_TIMESTAMPS
8
9 cd "$APP_ROOT"
10
11 if [ "$(id -u)" = "0" ]; then
12 mkdir -p storage/framework/cache/data \
13 storage/framework/sessions \
14 storage/framework/views \
15 storage/logs \
16 bootstrap/cache
17 chown -R www-data:www-data storage bootstrap/cache
18 chmod -R 775 storage bootstrap/cache
19 fi
20
21 if [ "${RUN_MIGRATIONS:-0}" = "1" ]; then
22 until php artisan db:monitor --max=1 >/dev/null 2>&1; do
23 sleep 2
24 done
25
26 php artisan migrate --force
27
28 if [ "${RUN_SEEDERS:-0}" = "1" ]; then
29 php artisan db:seed --force
30 fi
31
32 php artisan optimize
33 fi
34
35 exec "$@"
36
cesta: compose.yaml
45 řádků · 0.9 KB
Docker Compose konfigurace pro lokální vývoj s FrankenPHP a PostgreSQL 18 alpine.
1 name: agylaravel
2
3 services:
4 app:
5 build:
6 context: .
7 dockerfile: docker/Dockerfile
8 target: dev
9 ports:
10 - "8000:80"
11 volumes:
12 - .:/app
13 environment:
14 APP_ENV: local
15 APP_DEBUG: "true"
16 DB_CONNECTION: pgsql
17 DB_HOST: postgres
18 DB_PORT: 5432
19 DB_DATABASE: agy_laravel
20 DB_USERNAME: agy_laravel
21 DB_PASSWORD: secret
22 PHP_OPCACHE_VALIDATE_TIMESTAMPS: "1"
23 depends_on:
24 postgres:
25 condition: service_healthy
26
27 postgres:
28 image: postgres:18-alpine
29 environment:
30 POSTGRES_DB: agy_laravel
31 POSTGRES_USER: agy_laravel
32 POSTGRES_PASSWORD: secret
33 ports:
34 - "5432:5432"
35 volumes:
36 - postgres_data:/var/lib/postgresql
37 healthcheck:
38 test: ["CMD-SHELL", "pg_isready -U agy_laravel"]
39 interval: 5s
40 timeout: 5s
41 retries: 5
42 start_period: 5s
43
44 volumes:
45 postgres_data:
46
cesta: compose.prod.yaml
37 řádků · 0.7 KB
Produkční Docker Compose pro nasazení na Hetzner serveru s připojením na sdílenou síť db a caddy.
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
Zdroj: skutečné soubory této aplikace Plný průzkumník anatomie →