Nasazení Symfony je pokaždé stejný krátký seznam: zapnutý produkční režim, správné proměnné prostředí na svém místě, závislosti nainstalované bez vývojářských balíčků, zahřátá cache, zmigrovaná databáze, zapisovatelný adresář var/ a rozhodnutí, co bude spouštět tvé workery Messengeru. FrankenPHP tenhle seznam nemění. Mění server pod ním: místo nginxu, který mluví s PHP-FPM, obsluhuje HTTP a spouští PHP jediný proces postavený na Caddy. Pro aplikaci v Symfony je to většinou dobrá zpráva a tenhle návod se věnuje místům, kde to tak není.
Je psaný pro řadu Laravel / PHP na RE:NODE, která provozuje Laravel nebo Symfony na FrankenPHP s PHP 8.3, 8.4 nebo 8.5 a při startu instaluje závislosti přes Composer. Symfony část každého příkazu níže je na jakémkoli hostingu stejná. Část týkající se platformy - kam patří proměnné, jak se k tobě dostane proxy slot, co nabízí panel - je takto označená a to, co platforma nedokumentuje, je řečeno nahlas místo hádání.
Co se mění s FrankenPHP#
Klasický PHP stack má tři pohyblivé části: webový server (nginx nebo Apache), PHP-FPM a lepidlo mezi nimi. FrankenPHP to sloučí do jediné binárky. Vestavěný interpret PHP běží uvnitř webového serveru v Go postaveného na Caddy, takže máš jeden proces ke spuštění, jeden log ke čtení a žádný řádek fastcgi_pass, ve kterém by se dalo udělat chybu.
Pro aplikaci v Symfony to má tři praktické důsledky.
- Nemusíš přenášet žádnou konfiguraci nginxu. Bloky
try_filesafastcgi_split_path_infoz dokumentace Symfony existují proto, aby každý požadavek směrovaly napublic/index.php. Caddy má pro front controller vlastní idiom, který dělá totéž, a projekt v Symfony servírovaný přes FrankenPHP tvůj starý server blok nepotřebuje. - Worker mód existuje a je volitelný. V běžném režimu se FrankenPHP chová jako PHP-FPM: každý požadavek nastartuje framework, vyřídí se a všechno se zahodí. Ve worker módu se kernel Symfony nastartuje jednou a zůstává v paměti mezi požadavky, což může čas na požadavek výrazně zkrátit. Vyžaduje balíček
runtime/frankenphp-symfonya platí pro něj stejná varování jako pro každý dlouho žijící proces v PHP: statický stav a stav služeb se mezi požadavky přenáší, pokud se služby neresetují. Symfony resetuje většinu svých vlastních, ale tvůj kód je tvoje starost. Worker mód ber jako ladění až po tom, co aplikace funguje, ne jako podmínku nasazení. - Verze PHP je nastavení platformy. RE:NODE v této řadě nabízí 8.3, 8.4 a 8.5. Symfony 7.4 potřebuje PHP 8.2 nebo novější a Symfony 8.0 potřebuje PHP 8.4, takže před výběrem zkontroluj omezení
require.phpa verzesymfony/*ve svémcomposer.json. Vezmi nejnovější PHP, které celý tvůj strom závislostí dovolí.
Všechno ostatní v tomhle návodu je běžné Symfony. Pokud zvažuješ, kolik z toho se dá přenést z Laravelu, nasazení Laravelu do produkce pokrývá tu stranu frameworku a oba články se liší hlavně názvy: APP_ENV=prod oproti production, bin/console oproti artisan, Messenger oproti queue workeru.
Produkční režim: APP_ENV, APP_DEBUG a APP_SECRET#
Symfony se skoro o všem rozhoduje podle tří proměnných. V produkci vypadají takto:
APP_ENV=prodAPP_DEBUG=0APP_SECRET=replace-with-a-long-random-stringAPP_ENV=prod vybere konfiguraci prod a bloky when@prod v config/. Také zabrání frameworku, aby při každém požadavku znovu kompiloval kontejner a kontroloval časy změn šablon. APP_DEBUG=0 vypne debug kontejner a stránku s výjimkou, která vypisuje cesty k souborům, argumenty služeb a detaily prostředí. Spustit APP_ENV=prod s APP_DEBUG=1 jde, ale nedoporučuje se: dostaneš produkční cachování s vývojovým únikem informací.
APP_SECRET slouží pro tokeny CSRF, podepsané URL, cookie remember-me a všechno ostatní, co Symfony podepisuje. Vygeneruj ho jednou a nech ho být, protože jeho změna zneplatní podepsané odkazy a odhlásí lidi. Rozumný způsob, jak ho vyrobit:
$ php -r 'echo bin2hex(random_bytes(32)), PHP_EOL;'Výchozí prostředí, když ho nic nenastaví, se bere z řádku APP_ENV v .env a recept ho dodává jako dev. To je první věc ke kontrole, když čerstvě nasazená aplikace ukazuje celému internetu lištu profileru Symfony: žádná skutečná proměnná soubor nepřebila.
.env, .env.local a skutečné proměnné prostředí#
Komponenta Dotenv v Symfony načítá soubory v pevně daném pořadí a když ho pochopíš, zmizí většina hlášení typu „proč to používá špatnou databázi“. Soubory od nejnižší po nejvyšší prioritu:
| Soubor | Commitovat? | Účel |
|---|---|---|
.env | Ano | Výchozí hodnoty a dokumentace každé proměnné |
.env.local | Ne | Přepsání specifická pro daný stroj, nečte se při APP_ENV=test |
.env.prod | Ano | Výchozí hodnoty pro prostředí prod |
.env.prod.local | Ne | Přepsání prod specifická pro daný stroj |
Nad všemi těmi soubory stojí skutečné proměnné prostředí: hodnoty nastavené procesem, který spouští PHP. Dotenv nikdy nepřepíše proměnnou, která už existuje, takže skutečná proměnná vždy vyhraje nad jakýmkoli souborem. Přesně tuhle vlastnost chceš v produkci. Soubory jsou pohodlí pro vývoj; zdrojem pravdy je prostředí samotného serveru.
Co z toho plyne:
- Do
.envcommituj jen bezpečné zástupné hodnoty. Dokumentuje názvy proměnných a je základem. - Do
.envnedávej produkční tajné údaje a.env.localnikdy necommituj. Cokoli, co se dostane do gitu, ber jako uniklé. - Na RE:NODE nastav produkční hodnoty jako proměnné prostředí na záložce Startup serveru. Jsou to skutečné proměnné, takže přepíšou soubory. Proměnné prostředí a tajné údaje rozebírá, co kam patří a co nelogovat.
- Pokud dáváš přednost souboru, vytvoř
.env.localna serveru přes správce souborů nebo SFTP. Funguje to, ale leží v adresáři projektu, takže ho vynech z každého archivu, který někomu pošleš.
Dvě alternativy přímo ze Symfony stojí za znalost. První je composer dump-env prod, který zkompiluje soubory .env do jediného pole .env.local.php, jež se při startu načítá bez parsování textu. Spusť ho na stroji, který bude aplikaci obsluhovat, jako součást nasazení, a pamatuj, že hodnoty zmrazí: když změníš proměnnou, musíš příkaz spustit znovu. Druhá je trezor tajných údajů, php bin/console secrets:set DATABASE_PASSWORD --env=prod, který ukládá zašifrované hodnoty do config/secrets/prod/, a ty se dají commitovat. Na serveru pak stačí jediná skutečná proměnná - dešifrovací klíč SYMFONY_DECRYPTION_SECRET - a příkaz php bin/console secrets:decrypt-to-local --env=prod umí z trezoru udělat lokální soubor, kdykoli ho budeš chtít.
Composer při startu a produkční instalace#
V této řadě aplikací Composer instaluje závislosti při startu serveru, takže nový composer.lock se projeví při dalším restartu. Pravidlo na straně Symfony je stejné jako všude: commituj composer.lock, aby instalace vyřešila přesně ty verze, které jsi otestoval. Bez lock souboru se při každém startu omezení řeší znovu a verze, kterou provozuješ v produkci, je ta, kterou ráno nabídl Packagist.
Produkční instalace by měla být ekvivalentní tomuto příkazu:
$ composer install --no-dev --optimize-autoloader --no-interaction--no-dev přeskočí balíčky z require-dev: profiler, maker bundle, PHPUnit, ladicí nástroje. --optimize-autoloader vytvoří class map, aby PHP při každém autoloadu neprohledávalo souborový systém. V aplikacích Symfony má composer.json navíc sekci auto-scripts, kterou recept zapojí do post-install-cmd, takže se po každé instalaci spustí cache:clear a assets:install public. Proto jsou instalace a krok s cache provázané.
Z toho plyne klasická chyba. Pokud APP_ENV není při dokončení Composeru prod a dev balíčky chybí, post-install cache:clear se pokusí sestavit kontejner dev, zjistí, že Symfony\Bundle\DebugBundle\DebugBundle z dev záznamů v config/bundles.php chybí, a skončí chybou. Nastav APP_ENV=prod v prostředí dřív, než instalace běží, ne potom, a chyba se neobjeví. Pokud na stagingovém serveru potřebuješ dev balíčky, nech je tam a APP_ENV=dev tam nastav záměrně.
Platforma nedokumentuje, jaké přepínače používá instalace při startu, takže si výstup konzole přečti při prvním startu a nic nepředpokládej. Konzole ukazuje výstup instalace živě a nefiltrovaně. Čtení konzole vysvětluje historii, sbalený šum a graf paměti, který budeš chtít mít při prvním startu otevřený.
Front-endové assety jsou druhý build krok. S AssetMapperem php bin/console asset-map:compile zapíše soubory s otisky do public/assets/. S Webpack Encore nebo Vite builduj na vlastním stroji nebo v CI a nahraj výstup: instalovat Node a kompilovat velký bundle na plánu s 1 GB je spolehlivý způsob, jak narazit na limit paměti.
Vyčištění a zahřátí cache#
Produkční cache žije v var/cache/prod/. Obsahuje zkompilovaný kontejner služeb, směrovací tabulku, zkompilované šablony Twigu, metadata validátoru a serializéru a proxy třídy Doctrine. Všechno je to generované, nic z toho nepatří do gitu a zastaralá kopie je příčinou překvapivě velké části hlášení „nasadil jsem, ale nic se nezměnilo“.
Důležité jsou dva příkazy:
$ php bin/console cache:clear$ php bin/console cache:warmupV prostředí prod cache:clear cache přestaví a zároveň zahřeje v rámci téhož běhu, takže obvykle stačí jeden příkaz; cache:warmup existuje pro zahřátí bez čištění a --no-warmup u cache:clear zahřátí přeskočí. Důvod zahřívat při nasazení, a ne nechat to na prvním požadavku, je jednoduchý: první požadavek po studené cache kompiluje kontejner a může trvat mnoho sekund a spotřebovat několik set megabajtů paměti. Na malém plánu může taková špička sama dosáhnout limitu.
Dva návyky, díky kterým je to bez dramat:
- Sestav cache tam, kde se bude servírovat. Zkompilovaný kontejner obsahuje absolutní cesty. Cache sestavená na tvém notebooku a nahraná na server ukazuje do adresářů, které na serveru neexistují.
- Nemíchej uživatele. Pokud příkaz spuštěný jako jeden uživatel vytvoří
var/cache/prod/a webový proces běží jako jiný, ten druhý do něj nemůže zapisovat. Konzole panelu a proces serveru sdílejí uživatele kontejneru, což problém odstraňuje; SFTP session pod jiným uživatelem ho může znovu vyrobit, takže po nahrání cache vyčisti z konzole.
Je také dobré vědět, že právě cachovaná konfigurace je důvod, proč se změněná proměnná prostředí zdánlivě nijak neprojeví. Symfony vyhodnocuje hodnoty %env()% za běhu, takže běžné proměnné se do cache nepečou, ale cokoli zkompilovaného z kontejneru, třeba parametr odvozený v services.yaml, ano. Když si nejsi jistý, vyčisti.
Migrace Doctrine#
Změny schématu se dodávají jako třídy migrací v migrations/ a produkční příkaz je:
$ php bin/console doctrine:migrations:migrate --no-interaction --allow-no-migration--no-interaction zastaví potvrzovací dotaz, který by neinteraktivní nasazení zablokoval. --allow-no-migration způsobí, že příkaz skončí úspěšně, když není co spouštět, takže se nasazovací skript nezhroutí v běžném případě vydání bez změn schématu. Před prvním nasazením a kdykoli si nejsi jistý vypíše doctrine:migrations:status nakonfigurovanou databázi, aktuální verzi a počet čekajících migrací a doctrine:schema:validate porovná tvé mapování se skutečným schématem.
V produkci nikdy nepoužívej doctrine:schema:update --force. Spočítá rozdíl a aplikuje ho v jednom kroku, bez kontroly a bez záznamu. Migrace generuj ve vývoji přes php bin/console doctrine:migrations:diff, přečti si SQL, commitni ho a produkce ať spouští jen commitované migrace.
Připojení nese proměnná DATABASE_URL. Schéma odpovídá databázovému enginu a host, port, uživatel a heslo pocházejí z místa, kde byla databáze vytvořena:
DATABASE_URL="postgresql://app:secret@db.example.net:5432/app?serverVersion=16&charset=utf8"Pro MySQL je schéma mysql:// a serverVersion je skutečná verze serveru, ne odhad: Doctrine podle ní vybírá vlastnosti dialektu SQL a špatná hodnota vyrobí migrace, které selhávají podivnými způsoby. Na RE:NODE má aplikační server sloty pro databáze vytvářené v panelu s vygenerovaným hostem, uživatelem a heslem a samostatná řada databází nabízí PostgreSQL, MySQL 8.4 LTS a další na vlastním hostu a portu. Hodnoty vezmi z panelu a pokud heslo obsahuje znaky jako @, / nebo :, zakóduj ho do URL, jinak Doctrine URL rozparsuje na nesmysl. Zálohy a obnova databází vysvětluje záložní polovinu příběhu.
Záleží na pořadí. Vydání, které nasadí kód potřebující nový sloupec dřív, než proběhla migrace, dává každému návštěvníkovi chybu 500 po celou dobu té mezery. U čehokoli většího než malý web piš zpětně kompatibilní migrace: přidej sloupec jako nullable, nasaď kód, který ho snese, doplň data a zpřísni to až v pozdějším vydání. Migrace bez výpadku ten postup rozebírá. Před každou migrací, která něco maže nebo přejmenovává, udělej zálohu; na RE:NODE ji záložka Backups vytvoří na požádání a obnoví tlačítkem.
Workery Messengeru, transport a scheduler#
Cokoli pomalého v požadavku - e-mail, zpracování obrázků, volání třetích stran - patří na transport Messengeru a transport potřebuje consumer proces, který přežije požadavek. Nejdřív vyber transport v .env:
MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0Transport Doctrine ukládá zprávy do databázové tabulky a nepotřebuje nic navíc. V produkci nastav auto_setup=0 a tabulku jednou vytvoř příkazem php bin/console messenger:setup-transports, aby se aplikace za běhu nikdy nepokoušela měnit schéma. Pro vyšší propustnost je dalším krokem transport postavený na Valkey (redis:// v DSN, protože Valkey mluví protokolem Redis) - viz fronty úloh s Valkey.
Consumer je dlouho běžící příkaz:
$ php bin/console messenger:consume async --time-limit=3600 --memory-limit=128MPodstatné jsou ty dva limity. --time-limit=3600 způsobí, že worker po hodině skončí, a --memory-limit=128M, že skončí, jakmile přeroste tuto hranici, což tě chrání před pomalým únikem paměti v kódu, který nemáš pod kontrolou. Očekává se, že ho supervisor znovu nastartuje, a to je část, na kterou si musí odpovědět každé nasazení v kontejneru samo.
Server na RE:NODE je jeden kontejner s jedním procesem. Záložky panelu jsou na tom postavené, takže poctivý návrh je brát consumer jako samostatný server: druhý aplikační server ve stejné řadě, se stejným repozitářem, stejnými proměnnými prostředí a stejnou databází, spuštěný příkazem, který místo webového serveru spouští worker. Na záložce Startup svého plánu ověř, zda jde startovací příkaz změnit, protože text platformy tam potvrzuje proměnné prostředí a úpravu příkazu nepopisuje. Pokud to nejde, zbývají alternativy: transport messengeru konzumovaný malou naplánovanou konzolovou úlohou, nebo při nízkém objemu transport sync, než se rozhodneš. Nespouštěj worker jako proces na pozadí uvnitř konzolové session samotného webového serveru a nečekej, že přežije restart.
Po každém nasazení drží consumer v paměti starý kód. Řekni mu, ať skončí, příkazem php bin/console messenger:stop-workers, který nastaví příznak v poolu cache; každý worker dokončí aktuální zprávu a skončí a restart ho vrátí už na novém kódu. Neúspěšné zprávy jdou do failure transportu, pokud ho nakonfiguruješ, a potom:
$ php bin/console messenger:failed:show$ php bin/console messenger:failed:retryStejná dlouho běžící mašinérie pohání Symfony Scheduler: opakující se zprávy definuj přes #[AsPeriodicTask] nebo schedule providera a konzumuj transport scheduler_default příkazem messenger:consume scheduler_default. Nahradí crontab konzolových příkazů jedním workerem. Pokud dáváš přednost obyčejným výrazům cronu, výrazy cronu vysvětlené pokrývají těch pět polí a úlohy na pozadí na malém serveru porovnává možnosti pro velikost stroje, s níž tenhle návod počítá.
var/ musí být zapisovatelný a kam jdou logy#
Symfony za běhu zapisuje přesně do jednoho stromu: var/. V něm var/cache/ drží zkompilovanou cache a var/log/ logovací soubory a sessions končí v var/sessions/, pokud používáš souborový handler. Webový proces tam musí umět vytvářet a zapisovat soubory. Nic jiného v projektu zápis nepotřebuje, včetně src/, config/ a vendor/.
Příznak nezapisovatelného var/ je nezaměnitelný, jakmile ho jednou uvidíš: chyba 500 a v konzoli nebo ve var/log zpráva začínající Unable to create the "cache" directory nebo Failed to write file. Řešením je opravit vlastnictví, ne uvolnit oprávnění na 777:
$ chmod -R ug+rwX varLogování si zaslouží rozhodnutí. Konfigurace Monologu z receptu pro prod ukládá záznamy do bufferu přes handler fingers_crossed, který zapíše celý kontext požadavku, až když dorazí záznam úrovně error, a výsledek posílá do php://stderr v JSON. V kontejneru je to správná výchozí volba, protože stderr jde rovnou do konzole, kde ho čteš živě, a nic nezaplňuje disk. Pokud handler přepneš na stream do var/log/prod.log kvůli dlouhé retenci, rotuj ho: handler rotating_file v Monologu s hodnotou max_files zabrání tomu, aby log rostl, dokud se nezaplní disk plánu. Logy, které stojí za to nechat rozebírá, co uchovávat a jak dlouho.
Uploady jsou věc zvlášť. Soubory nahrané uživateli by měly ležet někde, kde je můžeš zálohovat, ne ve var/, které je z principu jednorázové. Dej je na cestu mimo strom cache nebo do objektového úložiště kompatibilního s S3 a zahrň tu cestu do plánu záloh. Záloha, kterou nikdo neobnovil, je hypotéza.
Za proxy slotem: trusted proxies a X-Forwarded-For#
Téměř každá produkční aplikace v Symfony stojí za něčím, co ukončuje TLS. Na RE:NODE zahrnují aplikační plány proxy slot: nasměruj záznam A na adresu, kterou ukazuje panel, a certifikát se vystaví a obnovuje za tebe, v okně 21 dní. Skutečná IP klienta dorazí v hlavičce X-Forwarded-For. Co dělá reverzní proxy vysvětluje celou cestu a HTTPS a Let's Encrypt vysvětleně pokrývá, proč vystavení selže, když DNS ještě není připravené.
Důsledek pro Symfony je, že přeposlaným hlavičkám ve výchozím stavu nevěří. Bez konfigurace vrací $request->getClientIp() adresu proxy, $request->isSecure() je false a UrlGeneratorInterface sestavuje odkazy http:// na stránce s HTTPS, které prohlížeče blokují jako smíšený obsah. Rate limitery a omezování přihlášení pak počítají každého návštěvníka jako jednoho klienta, což je horší, než to zní.
Řekni Symfony, kterým proxy věřit a které hlavičky číst, v config/packages/framework.yaml:
framework: trusted_proxies: '%env(TRUSTED_PROXIES)%' trusted_headers: - 'x-forwarded-for' - 'x-forwarded-host' - 'x-forwarded-proto' - 'x-forwarded-port' - 'x-forwarded-prefix'Potom nastav proměnnou. Symfony přijímá seznam IP adres a rozsahů CIDR oddělený čárkami a dvě speciální hodnoty: PRIVATE_SUBNETS věří loopbacku a privátním rozsahům a REMOTE_ADDR věří jakékoli adrese, ze které spojení přichází.
TRUSTED_PROXIES=PRIVATE_SUBNETSKlíčový je kompromis. REMOTE_ADDR je správně jen tehdy, když je proxy jediná cesta do aplikace, protože kdokoli, kdo se smí připojit, pak může podvrhnout X-Forwarded-For a vydávat se za kohokoli. Text platformy neuvádí adresu samotné proxy, takže spolehlivá metoda je empirická: nasaď routu, která vypíše $request->server->get('REMOTE_ADDR'), zatímco jsi přihlášený, přečti hodnotu a tuto adresu nebo rozsah dej do TRUSTED_PROXIES místo zástupného znaku. Pamatuj, že za důvěryhodnou proxy může hlavička obsahovat řetězec adres a Symfony vybere poslední nedůvěryhodnou, proto musí být seznam správný, a ne jen široký.
Nastav také DEFAULT_URI nebo default_uri routeru (starší zápis jsou parametry router.request_context.host a scheme), aby URL generované v konzolových příkazech a handlerech Messengeru, které nemají požadavek, z něhož by schéma přečetly, používaly https:// a tvůj skutečný hostname.
Health route a co sledovat#
Symfony nemá vestavěný health endpoint, což se snadno napraví a stojí za to. Dobrý endpoint odpoví rychle, nesahá na nic pomalého a případně kontroluje databázi zvlášť:
<?phpnamespace App\Controller;use Doctrine\DBAL\Connection;use Symfony\Component\HttpFoundation\JsonResponse;use Symfony\Component\Routing\Attribute\Route;final class HealthController{ #[Route('/healthz', name: 'health', methods: ['GET'])] public function live(): JsonResponse { return new JsonResponse(['status' => 'ok']); } #[Route('/healthz/db', name: 'health_db', methods: ['GET'])] public function db(Connection $connection): JsonResponse { $connection->executeQuery('SELECT 1'); return new JsonResponse(['status' => 'ok']); }}Liveness route drž bez volání databáze, aby výpadek databáze nepřiměl monitoring k závěru, že webový proces je mrtvý. Dej pozor, aby konfigurace firewallu nechávala /healthz veřejné (například ^/healthz v access_control s PUBLIC_ACCESS) a aby ho nezachytil prefix locale. Graceful shutdown a health checky rozebírá, co sondovat a proč.
Stejně důležitý je pohled samotného panelu. Graf v konzoli ukazuje paměť, CPU a disk proti limitům plánu a RE:NODE má dvě chování, která stojí za znalost u služby v PHP. Při dosažení limitu paměti jádro kontejner zastaví a ten se čistě restartuje místo swapování, takže únik paměti nebo příliš velké zahřátí cache se projeví jako restart bez čehokoli v logu aplikace. A watcher každé dvě minuty kontroluje, zda uptime nešel zpátky: tři restarty za hodinu vyvolají varování na stránce serveru a automatický ticket a šest vede k pozastavení. Crash loop po špatném nasazení, třeba kvůli chybějící migraci, je proto něco, co je třeba rychle zastavit a opravit, ne sledovat.
Plán dimenzuj měřením, ne nadějí. Aplikace v Symfony v režimu prod bez worker módu běžně spotřebuje desítky megabajtů na požadavek a první požadavek po studené cache několikrát víc; dimenzování webové aplikace na den spuštění z toho dělá metodu a nastavení PHP, na kterých záleží pokrývá memory_limit a OPcache.
Nasazení: git, SFTP a stránka po prvním startu#
Kód se na server dostává dvěma způsoby a oba jsou v panelu.
- Git deploy. Pouze GitHub, přes GitHub App s krátkodobými tokeny, takže fungují i soukromé repozitáře. Dva přepínače: pull při každém startu a deploy při push, který restartuje běžící server a spustí zastavený. Každé nasazení má vlastní záznam. Protože Composer instaluje při startu, tahle kombinace dává „push, restart, nové závislosti“ bez build serveru.
- Správce souborů nebo SFTP. Přihlašovací údaje SFTP jsou na server, ne na účet. Správce souborů rozbaluje archivy na místě, což se hodí pro nahrání zazipovaného vydání. Nenahrávej
vendor/,var/ani žádný.env.localze svého stroje.
Nový server v této řadě začíná na své adrese s malou funkční stránkou, dokud nenasadíš kód, a ta stránka nikdy nepřepíše soubory, které jsi nahrál, takže první, co na adrese uvidíš, je zástupná stránka, ne tvoje aplikace. Jakmile je kód na místě, vypadá postup nasazení, který můžeš pokaždé spustit stejně, takto:
$ composer install --no-dev --optimize-autoloader --no-interaction$ php bin/console doctrine:migrations:migrate --no-interaction --allow-no-migration$ php bin/console cache:clear$ php bin/console messenger:stop-workersSpouštěj to, kde to jde, z konzole panelu a drž každý krok pokaždé ve stejném pořadí: instalace, migrace, přestavění cache, restart workerů. Pokud migrace něco maže, udělej předtím zálohu. Pro nasazení, které je pro návštěvníky neviditelné, a ne jen rychlé, pokrývá nasazení bez výpadku na malém serveru vzor s adresáři vydání, s výhradou, že na plánu s jedním kontejnerem potřebuje víc disku a víc opatrnosti.
Řešení potíží#
Prázdná stránka nebo holá 500 v produkci. To je APP_DEBUG=0, který dělá svou práci. Přečti si nejdřív výstup konzole, protože Monolog posílá chyby na stderr, potom var/log/prod.log, pokud jsi změnil handler. Když jsou obojí prázdné, PHP je nemohlo zapsat: zkontroluj oprávnění var/.
Objeví se lišta profileru nebo stránka s výjimkou ukazuje cesty k souborům. APP_ENV nebo APP_DEBUG má pořád vývojovou hodnotu. Nastav APP_ENV=prod a APP_DEBUG=0 jako skutečné proměnné a zkontroluj, že je nepřebíjí zatoulaný .env.local.php.
„Unable to create the cache directory“ nebo „Failed to write file“. var/ není zapisovatelný pro proces, který spouští PHP, nebo dřívější příkaz vytvořil var/cache pod jiným uživatelem. Oprav vlastnictví a pak z konzole spusť cache:clear.
„Class DebugBundle not found“ po nasazení. Dev balíčky byly odstraněny přes --no-dev, ale kontejner byl sestaven v prostředí dev. Nastav APP_ENV=prod dřív, než se spustí Composer.
Všechno funguje kromě odkazů, které jsou http://, a smyček přesměrování při přihlášení. Trusted proxies nejsou nakonfigurované, takže Symfony nevidí HTTPS. Nastav TRUSTED_PROXIES a seznam trusted_headers uvedený výše.
Změna proměnné prostředí nic neudělá. Skutečná proměnná potřebuje restart serveru, aby se k PHP dostala. Pokud používáš i composer dump-env, zkompilovaný .env.local.php pořád drží starou hodnotu. Spusť dump znovu nebo ho smaž.
„An exception occurred in the driver: could not translate host name“. Host v DATABASE_URL je špatně, nebo heslo obsahuje nezakódované speciální znaky. Zkopíruj host a port z panelu a heslo zakóduj.
Zprávy leží ve frontě a nikdo je nekonzumuje. Nebeží žádný consumer, nebo běží se starým kódem. Spusť ho a udělej z messenger:stop-workers součást každého nasazení, aby se dlouho žijící worker vyměnil.
Server se sám restartuje a v logu nic není. Limit paměti. Podívej se na graf paměti kolem času restartu. Sniž --memory-limit u workeru, zahřívej cache při nasazení, ne při prvním požadavku, a přečti si Linux swap a OOM killer, co zastavení jádrem znamená.
FAQ#
Potřebuju worker mód, abych mohl provozovat Symfony na FrankenPHP?
Ne. FrankenPHP servíruje Symfony ve svém běžném režimu, s jedním startem na požadavek, přesně jako PHP-FPM. Worker mód je volba pro výkon, která drží kernel v paměti mezi požadavky. Zapni ho, až bude aplikace stabilní, a otestuj, zda se stav nepřenáší z jednoho požadavku do dalšího.
Kde mají žít produkční tajné údaje: v .env, nebo ve skutečných proměnných prostředí?
Ve skutečných proměnných prostředí nebo v trezoru tajných údajů Symfony. .env se commituje a dokumentuje názvy. Skutečná proměnná vždy vyhraje nad jakýmkoli souborem, protože Dotenv existující hodnoty nepřepisuje. Na RE:NODE se proměnné prostředí zadávají na záložce Startup.
Je composer install --no-dev pro aplikaci v Symfony bezpečný?
Ano, pokud se produkční kód nespoléhá na žádný dev balíček a APP_ENV=prod je nastaveno před instalací. Pokud chyba „class not found“ jmenuje balíček, patří do require, ne do require-dev. Maker, profiler a PHPUnit mají zůstat v require-dev.
Proč Symfony vidí IP proxy místo návštěvníka?
Protože přeposlaným hlavičkám nevěří, dokud mu neřekneš jinak. Nastav framework.trusted_proxies na adresu nebo rozsah proxy, uveď hlavičky x-forwarded-* v trusted_headers a getClientIp() vrátí adresu z X-Forwarded-For. Nevěř všemu, pokud je tvůj origin dosažitelný přímo.
Spouští se migrace Doctrine při nasazení automaticky?
Samy od sebe ne. Musíš doctrine:migrations:migrate spustit jako krok a udělat ho neinteraktivní přes --no-interaction. Tam, kde změna není zpětně kompatibilní, ho spusť dřív, než nový kód začne obsluhovat provoz, a před destruktivními migracemi udělej zálohu.
Může worker Messengeru běžet na stejném serveru jako web?
Server je jeden kontejner s jedním procesem, takže worker plánuj jako druhý server se stejným kódem, proměnnými a databází. Zda jde na plánu změnit startovací příkaz, ověř na jeho záložce Startup dřív, než se k takovému uspořádání zavážeš.


