Jak funguje entrypoint.sh, čekání na databázi (db:monitor) a compose.yaml
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:
- Ověřit a nastavit Linuxová oprávnění: Zajistit, že adresáře
storage/abootstrap/cache/existují a webserverový uživatelwww-datado nich může zapisovat logy, session a cache. - 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.
- Spustit migrace a seedy: Bezpečně a idempotentně uvést databázové schéma do aktuálního stavu.
- Zahřát produkční cache: Provést
php artisan optimize. - 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ř.
SIGTERMpřidocker compose stop) by se nedostaly k FrankenPHP. - S
execwebserver signál okamžitě zachytí, dokončí rozpracované HTTP požadavky a korektně se ukončí (Graceful Shutdown).
#!/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.
Proč v entrypoint.sh používáme smyčku until php artisan db:monitor namísto okamžitého volání migrate?
Co je hlavním účelem volání php artisan optimize při startu produkčního kontejneru?
Proč musí startovací skript kontejneru končit příkazem exec "$@"?
| 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 | |