RE:NODE

Strapi v produkci: proměnné prostředí, databáze, uploady a upgrady

Text: Tým RE:NODE
15 min čtení


Provoz Strapi v produkci se dá shrnout do šesti rozhodnutí, která uděláš jednou a zapíšeš si je: sada tajných klíčů v .env, které se po spuštění už nikdy nemění; databáze, kterou umíš zálohovat (SQLite pro jeden menší web, PostgreSQL pro všechno, kde jsou redaktoři a provoz); místo pro nahrané soubory, které přežije nový build; administrace sestavená s NODE_ENV=production; veřejné url, které odpovídá tomu, co obsluhuje reverzní proxy; a postup upgradu, který začíná zálohou. Na pohodlný build administrace potřebuje Strapi zhruba 2 GB paměti, na samotné obsluhování požadavků pak mnohem méně. Když těchhle šest věcí uděláš správně, je Strapi tichý proces Node na portu 1337. Když jednu pokazíš, zjistíš to v den, kdy se redaktor přihlásí a všechny API tokeny přestanou fungovat.

Tento návod popisuje Strapi 5, aktuální hlavní verzi, a upozorňuje, kde se liší Strapi 4. Názvy souborů jsou uvedené jako .js; projekt v TypeScriptu má tytéž soubory jako .ts.

Jak je aplikační server se Strapi poskládaný#

Strapi je aplikace v Node.js ze dvou polovin, které běží v jednom procesu. První je API pro obsah: REST (a GraphQL, pokud přidáš plugin) pod /api, vygenerované z typů obsahu, které sis nadefinoval. Druhá je administrace, aplikace v Reactu na adrese /admin, ve které redaktoři píšou obsah a ve které ty při vývoji vytváříš typy obsahu.

Tohle rozdělení vysvětluje nejpřekvapivější pravidlo produkčního Strapi: Content-Type Builder funguje jen ve vývojovém režimu. strapi develop sleduje projekt a při změně modelu se restartuje; strapi start to nedělá a builder je jen pro čtení. Typy obsahu jsou kód - JSON schémata v src/api a src/components - a patří do Gitu. Měníš je na vývojové kopii, commitneš a nasadíš, přesně jako migraci databáze v jakémkoli jiném frameworku.

Při startu Strapi porovná tato schémata s databází a srovná tabulky podle nich. Je to pohodlné, a zároveň je to věc, na kterou je třeba dávat pozor: přejmenované pole je pro databázi jedno smazané pole a jedno nové a data ze starého sloupce za novým názvem nepřejdou. Pole přejmenovávej nejdřív na kopii produkčních dat, nikdy na živém webu.

Tři příkazy, které budeš používat; všechny jsou definované v souboru package.json, se kterým se nový projekt vygeneruje:

PříkazCo dělá
npm run developVývojový režim, automatický restart, Content-Type Builder zapnutý
npm run buildSestaví administraci (a zkompiluje TypeScript)
npm run startProdukční režim, bez automatického restartu, bez builderu
HTTPS 443HTTP 1337upload providerodkazy na médiaProhlížečweb nebo redaktorReverzní proxyHTTPS, tvoje doménaStrapiNode, port 1337DatabázeSQLite nebo PostgreSQLBucket S3nahraná média
Cesta požadavku z prohlížeče do Strapi a k jeho datům

Proměnné prostředí a tajné klíče#

Nový projekt dostane soubor .env s těmito položkami, z nichž každou vyplní generátor projektu náhodnou hodnotou. V produkci musí být nastavená každá z nich, jedinečná pro dané prostředí a od dne spuštění neměnná.

ProměnnáKdo ji čteCo se rozbije, když se změní
APP_KEYSconfig/server.jsPodepsané session cookies přestanou platit
API_TOKEN_SALTconfig/admin.jsPřestanou fungovat všechny API tokeny
ADMIN_JWT_SECRETconfig/admin.jsVšichni administrátoři jsou odhlášeni
TRANSFER_TOKEN_SALTconfig/admin.jsPřestanou fungovat transfer tokeny
JWT_SECRETconfig/plugins.jsPřihlášení uživatelů (Users and Permissions) vyprší

APP_KEYS je seznam oddělený čárkami, ne jedna hodnota. Strapi ho čte pomocí env.array('APP_KEYS') a klíči podepisuje cookies; víc klíčů umožňuje jejich rotaci. API_TOKEN_SALT slouží k hashování API tokenů před uložením, a proto jeho změna potichu zneplatní každý token, který drží tvůj frontend, build pipeline nebo mobilní aplikace. JWT_SECRET patří pluginu Users and Permissions - tomu, který vydává tokeny lidem registrovaným na tvém webu, na rozdíl od redaktorů v administraci. Novější projekty na Strapi 5 generují i ENCRYPTION_KEY pro šifrovaná tajemství administrace; pokud ho tvůj .env obsahuje, zacházej s ním stejně.

Zbytek souboru jsou běžná nastavení za běhu:

.env
HOST=0.0.0.0PORT=1337NODE_ENV=productionAPP_KEYS=key1base64==,key2base64==,key3base64==,key4base64==API_TOKEN_SALT=replace-with-random-base64ADMIN_JWT_SECRET=replace-with-random-base64TRANSFER_TOKEN_SALT=replace-with-random-base64JWT_SECRET=replace-with-random-base64STRAPI_TELEMETRY_DISABLED=true

Pro vygenerování hodnoty stačí jakýkoli zdroj 16 a více náhodných bajtů zakódovaných v base64:

bash
$ openssl rand -base64 32$ node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"

Když APP_KEYS chybí, Strapi odmítne nastartovat s chybou middlewaru strapi::session, která říká, že klíče aplikace jsou povinné. To je dobré selhání: hlasité, při startu a zřejmé. Špatná selhání jsou ta tichá popsaná výše, ke kterým dojde, když někdo na běžícím webu přegeneruje klíče. Článek proměnné prostředí a tajné klíče popisuje obecnou disciplínu, jak je držet mimo kód a mimo logy.

SQLite nebo PostgreSQL#

Strapi podporuje SQLite, PostgreSQL a MySQL; volí se proměnnou DATABASE_CLIENT a nastavuje v souboru config/database.js. Vygenerovaný soubor čte všechna nastavení připojení z prostředí, takže přechod je jen otázkou proměnných a instalace správného ovladače.

ProměnnáK čemu sloužíVýchozí hodnota ve vygenerované konfiguraci
DATABASE_CLIENTsqlite, postgres nebo mysqlsqlite
DATABASE_FILENAMECesta k souboru SQLite.tmp/data.db
DATABASE_HOSTAdresa serverulocalhost
DATABASE_PORTPort serveru5432 pro PostgreSQL
DATABASE_NAMENázev databázestrapi
DATABASE_USERNAMEUživatelské jménostrapi
DATABASE_PASSWORDHeslostrapi
DATABASE_SSLTLS k databázifalse
DATABASE_SCHEMASchéma v PostgreSQLpublic
DATABASE_POOL_MIN / MAXPool připojení2 / 10

SQLite je jeden soubor a databáze běží přímo v procesu. Pro marketingový web, portfolio nebo CMS pro dokumentaci s jedním či dvěma redaktory a frontendem, který se sestavuje staticky nebo se cachuje, úplně stačí: čtení je rychlé, nejde se přes síť a nic dalšího se nemusí spouštět ani platit. Její limity jsou zápisy a kopírování. Zápisy se řadí za sebe, takže vytížená administrace spolu s importem spouštěným webhookem bude čekat ve frontě. A soubor je živý: kopie data.db pořízená ve chvíli, kdy Strapi zapisuje, se nemusí dát otevřít.

Všimni si výchozí cesty. .tmp/data.db leží ve složce, jejíž název přímo svádí ke smazání, takže na dlouhodobě běžícím serveru ji buď vědomě nech být, nebo databázi přesuň pomocí DATABASE_FILENAME=data/strapi.db a ověř, že složka existuje.

PostgreSQL je správná volba, jakmile se objeví cokoli z tohoto: několik redaktorů pracujících současně, veřejné API se skutečným provozem, obsah importovaný skripty nebo potřeba konzistentních záloh přes pg_dump, zatímco web běží dál. V projektu k tomu potřebuješ balíček pg:

bash
$ npm install pg
.env
DATABASE_CLIENT=postgresDATABASE_HOST=db.example.netDATABASE_PORT=5432DATABASE_NAME=strapiDATABASE_USERNAME=strapiDATABASE_PASSWORD=long-random-passwordDATABASE_SSL=false

Pokud dáváš přednost jednomu připojovacímu řetězci, čte se i DATABASE_URL. DATABASE_SSL=true nastav, když se k databázi připojuješ přes internet, a ne přes privátní síť; článek PostgreSQL sslmode vysvětluje, co která úroveň skutečně ověřuje. Výchozí pool deseti připojení jednomu procesu Strapi stačí; pokud jich běží víc, sečti jejich pooly a porovnej je s limitem připojení databáze, jak vysvětluje článek pooly připojení a limity.

Přesun existujícího webu ze SQLite na PostgreSQL se dělá vlastním exportem a importem Strapi, ne převodem souboru: exportuj z instance se SQLite, nasměruj proměnné na prázdnou databázi PostgreSQL, jednou spusť Strapi, aby vytvořil tabulky, zastav ho a pak importuj. Příkazy najdeš níže v části o zálohách.

Nahrané soubory: lokální disk, nebo bucket S3#

Ve výchozím stavu ukládá plugin Upload soubory do public/uploads a Strapi je obsluhuje sám. Na jednom serveru s trvalým diskem to funguje a přesně tak startuje Strapi instalovaný jedním kliknutím. K objektovému úložišti tě dotlačí tři věci: disk, který se plní obrázky a vygenerovanými náhledy, proces nasazení, který nahrazuje složku projektu, a snaha obsluhovat média tak, aby každý požadavek nemusel projít přes Node.

Oficiální poskytovatel S3 pro Strapi je @strapi/provider-upload-aws-s3. Navzdory názvu funguje s jakoukoli službou kompatibilní s S3, pokud mu zadáš endpoint a u většiny služeb mimo AWS i adresování ve stylu path-style.

bash
$ npm install @strapi/provider-upload-aws-s3
config/plugins.js
module.exports = ({ env }) => ({  upload: {    config: {      provider: 'aws-s3',      providerOptions: {        baseUrl: env('S3_PUBLIC_URL'),        s3Options: {          credentials: {            accessKeyId: env('S3_ACCESS_KEY_ID'),            secretAccessKey: env('S3_ACCESS_SECRET'),          },          region: env('S3_REGION'),          endpoint: env('S3_ENDPOINT'),          forcePathStyle: true,          params: {            Bucket: env('S3_BUCKET'),          },        },      },      actionOptions: {        upload: {},        uploadStream: {},        delete: {},      },    },  },});

Názvy proměnných za env( si volíš sám; pevná je jen struktura. baseUrl je veřejná adresa, ze které se soubory obsluhují, a může se lišit od endpointu API, pokud před bucketem stojí CDN nebo proxy. forcePathStyle: true zajistí, že požadavky jdou na endpoint/bucket/key místo bucket.endpoint/key - článek path-style a virtual-hosted v S3 vysvětluje, proč to většina samostatně hostovaných úložišť potřebuje.

Krok, který vynechá skoro každý, je Content Security Policy administrace. Middleware strapi::security povoluje obrázky jen z vlastního původu, takže po změně poskytovatele se soubory nahrají, ale náhledy v Media Library jsou rozbité. Přidej hostitele svého bucketu do img-src a media-src v souboru config/middlewares.js:

config/middlewares.js
module.exports = [  'strapi::logger',  'strapi::errors',  {    name: 'strapi::security',    config: {      contentSecurityPolicy: {        useDefaults: true,        directives: {          'connect-src': ["'self'", 'https:'],          'img-src': ["'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'files.example.com'],          'media-src': ["'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'files.example.com'],          upgradeInsecureRequests: null,        },      },    },  },  'strapi::cors',  'strapi::poweredBy',  'strapi::query',  'strapi::body',  'strapi::session',  'strapi::favicon',  'strapi::public',];

Změna poskytovatele nepřesune existující soubory. Média nahraná před změnou dál ukazují na /uploads/... na lokálním disku; buď ty soubory nech na místě, nebo naplánuj přesun jako export, změnu poskytovatele a import.

Build administrace a paměť#

npm run build zkompiluje administraci do statických souborů. Spouští se jednou na nasazení, ne na každý požadavek, ale je to paměťově nejnáročnější věc, kterou Strapi dělá: sestavení aplikace v Reactu s administračním kódem všech pluginů může krátce potřebovat výrazně přes gigabajt, zatímco běžící server se potom často drží na pár stovkách megabajtů. Dokumentace Strapi k nasazení uvádí jako minimum 2 GB RAM.

Tři pravidla udrží build mimo potíže:

  1. Builduj s nastaveným NODE_ENV=production, aby byla administrace minifikovaná a sestavená pro produkční konfiguraci.
  2. Builduj znovu po všem, co mění administraci: nový plugin, upgrade Strapi, změna config/admin.js, změna cesty k administraci nebo veřejného URL.
  3. Když build spadne s chybou haldy JavaScriptu, zvyš limit Node jen pro tento příkaz pomocí NODE_OPTIONS=--max-old-space-size=1536 (pod velikostí paměti serveru) a přečti si limity paměti v Node, abys věděl, proč výchozí hodnota není RAM tvého tarifu.

Na serveru s pevným limitem paměti se build, který ho překročí, nezpomalí a nevzpamatuje se; proces je ukončen. Příznakem je build, který se zastaví v půlce bez vlastní chybové hlášky. Tohle se řeší větším tarifem pro build nebo výše uvedeným příznakem haldy, ne opakováním.

Veřejné URL, cesta k administraci a reverzní proxy#

Strapi naslouchá na HOST a PORT - ve výchozím stavu 0.0.0.0:1337 - a počítá s tím, že před ním stojí reverzní proxy kvůli TLS a doméně. Dvě nastavení v souboru config/server.js zajistí, že se tam bude chovat správně:

config/server.js
module.exports = ({ env }) => ({  host: env('HOST', '0.0.0.0'),  port: env.int('PORT', 1337),  url: env('PUBLIC_URL', 'https://cms.example.com'),  app: {    keys: env.array('APP_KEYS'),  },});

url je veřejná adresa webu včetně schématu. Strapi z ní generuje absolutní odkazy - vlastní adresu administrace, URL souborů z lokálního poskytovatele uploadů, odkazy v e-mailech pro obnovu hesla - takže když ji nenastavíš, vzniknou odkazy http://0.0.0.0:1337/..., které fungují jen na samotném serveru. Pokud Strapi obsluhuješ pod podcestou, třeba https://example.com/cms, uveď cestu v url a administraci znovu sestav.

Druhé nastavení je důvěra k proxy. Za proxy vidí Strapi každý požadavek jako obyčejné HTTP z adresy proxy, dokud mu neřekneš, aby důvěřoval hlavičkám X-Forwarded-For a X-Forwarded-Proto. Volba se jmenuje proxy v souboru config/server.js; její tvar se mezi Strapi 4 (logická hodnota) a Strapi 5 (objekt se samostatnými klíči) změnil, proto se podívej na stránku konfigurace serveru pro svou verzi a nekopíruj starý úryvek. Bez ní je klasickou chybou Cannot send secure cookie over unencrypted connection, když se proces přihlášení pokusí nastavit zabezpečenou cookie.

Administrace je ve výchozím stavu na cestě /admin a její cesta se nastavuje v souboru config/admin.js. Přesunutí ji nezabezpečí - API odpovídá tak jako tak - ale ubere to šum od skenerů. Článek co dělá reverzní proxy rozebírá hlavičky podrobně, a pokud před Strapi provozuješ vlastní nginx, v návodu na reverzní proxy s nginx je funkční blok. Pamatuj, že client_max_body_size má v nginx výchozí hodnotu 1m, což zablokuje každý upload větší než jeden megabajt odpovědí 413 dřív, než ho Strapi vůbec uvidí.

Strapi poskytuje /_health, který odpoví prázdnou odpovědí 204, když server běží - hodí se pro monitoring dostupnosti, který se nemá přihlašovat ani volat API závislé na databázi.

Zálohy a obnova#

Vrstvy jsou dvě a chceš obě.

První je databáze plus nahrané soubory, zkopírované jako soubory. S PostgreSQL pořídí pg_dump konzistentní dump, zatímco Strapi dál běží - článek pg_dump a pg_restore probírá formáty i obnovu. U SQLite se bezpečná kopie souboru pořizuje při zastaveném Strapi, nebo nástrojem, který používá backup API samotné SQLite; obyčejná kopie souboru uprostřed zápisu je loterie. Uploady v public/uploads jsou obyčejné soubory. Spolu s kódem projektu a tvým .env tvoří tyto tři části úplnou obnovu.

Druhá vrstva jsou vlastní příkazy Strapi pro přenos dat, které vytvoří přenositelný archiv nezávislý na databázovém stroji:

bash
$ npm run strapi export -- --no-encrypt -f strapi-backup$ npm run strapi import -- -f strapi-backup.tar.gz

Bez --no-encrypt se archiv zašifruje klíčem, na který se tě příkaz zeptá, a dostane příponu .tar.gz.enc. Archiv obsahuje obsah, nahrané soubory a konfiguraci projektu. Neobsahuje administrátorské účty ani API tokeny, takže nenahrazuje zálohu databáze - je to nástroj pro přechod mezi SQLite a PostgreSQL nebo mezi servery. Import před zápisem smaže existující obsah v cílové instanci, takže ho nikdy nenasměruj na špatnou. strapi transfer dělá totéž naživo mezi dvěma běžícími instancemi Strapi pomocí transfer tokenu, a právě ten chrání TRANSFER_TOKEN_SALT.

Ať zvolíš cokoli, aspoň jednou zálohu někde obnov. Článek zálohy, které se opravdu dají obnovit vysvětluje, proč je záloha, kterou nikdo neobnovil, jen hypotéza.

Bezpečný upgrade Strapi#

Strapi vydává nové verze často a administrace se při každém upgradu sestavuje znovu, takže upgrady si zaslouží zavedený postup, ne npm update. Strapi 5 obsahuje nástroj pro upgrade, který zvýší verze balíčků a spustí transformace kódu:

bash
$ npx @strapi/upgrade patch --dry$ npx @strapi/upgrade minor$ npx @strapi/upgrade major

patch a minor zůstávají v rámci aktuální hlavní verze; major je cesta ze Strapi 4 na 5 a je to migrační projekt, ne aktualizace - mění se pluginy, vlastní kód i databáze. --dry ukáže, co by se stalo, aniž by cokoli zapsal.

Postup, který funguje:

  1. Zazálohuj databázi a nahrané soubory a archiv měj jinde než na tomto serveru.
  2. Proveď upgrade na vývojové kopii s čerstvou kopií produkčních dat.
  3. Spusť npm run build, nastartuj aplikaci a proklikej administraci i API, které používá tvůj frontend.
  4. Commitni výsledek včetně lock souboru a nasaď přesně tento commit.
  5. Na serveru znovu sestav administraci a spusť ji; sleduj log prvního startu, protože právě tehdy proběhnou změny schématu databáze.

Zkontroluj i verzi Node.js. Každé vydání Strapi podporuje určitý rozsah sudých LTS verzí Node; běh na liché verzi nebo na verzi, jejíž podpora skončila, je častou příčinou chyb instalace po upgradu.

Strapi na RE:NODE#

Na RE:NODE je Strapi aplikace na jedno kliknutí na stránce hostingu aplikací. Instaluje a sestavuje se při prvním startu serveru, data jsou v SQLite na serveru, takže první věc, kterou uděláš, je otevřít /admin a vytvořit si administrátorský účet. Tarify začínají na 2 GB paměti, což odpovídá tomu, co build administrace potřebuje. Soubor .env je ve složce projektu a dá se upravit ve správci souborů nebo přes SFTP; než adresu komukoli dáš, ověř, že každý tajný klíč z tabulky výše obsahuje dlouhou náhodnou hodnotu.

Panel pokrývá provozní části, které tento návod popisuje. Nasměruj A záznam na adresu zobrazenou u proxy slotu a certifikát se vystaví a obnovuje automaticky, přičemž skutečná IP klienta přichází v hlavičce X-Forwarded-For. Sloty pro zálohy jsou v každém tarifu, zálohy se spouštějí ručně nebo přes záložku Schedules a ukládají se mimo stroj - viz zálohy v návodech. Protože SQLite je živý soubor, naplánuj zálohy na klidnou hodinu nebo nejdřív zastav server. Až ti SQLite přestane stačit, server PostgreSQL ti dá hodnoty DATABASE_* k vložení a tarif objektového úložiště S3 endpoint, klíče a první bucket pro poskytovatele uploadů, s adresováním path-style a libovolným názvem regionu.

Při dosažení limitu paměti se server zastaví a čistě restartuje, místo aby swapoval - to je přesně příznak ukončeného buildu popsaný výše: když npm run build potichu skončí, je to tím.

Řešení problémů#

Přihlášení do administrace funguje, ale po nasazení jsou všichni odhlášeni. Změnil se ADMIN_JWT_SECRET, obvykle proto, že se vygeneroval nový .env. Obnov původní hodnotu.

Po migraci vrací každý API token `401`. API_TOKEN_SALT se liší od toho, se kterým byly tokeny vytvořeny. Obnov ho, nebo vytvoř nové tokeny a aktualizuj všechny klienty.

Náhledy v Media Library jsou rozbité, ale soubory se nahrávají. Hostitel bucketu chybí v img-src v souboru config/middlewares.js.

Odkazy v e-mailech a URL médií ukazují na `0.0.0.0:1337`. V souboru config/server.js není nastavené url. Nastav ho a administraci znovu sestav.

Administrace po upgradu ukazuje starou verzi. Administrace nebyla znovu sestavena. Spusť npm run build a restartuj.

Uploady nad 1 MB selhávají s `413`. Je to limit velikosti těla požadavku na reverzní proxy, ne Strapi.

Typy obsahu nejde v administraci upravovat. Jsi v produkčním režimu, a tak je to správně. Uprav je ve vývojovém režimu a nasaď.

FAQ#

Stačí SQLite pro Strapi v produkci?

Pro menší web s jedním nebo dvěma redaktory a frontendem, který se cachuje nebo sestavuje staticky, ano. Hůř zvládá mnoho souběžných zápisů a vyžaduje opatrnost při zálohování, takže na PostgreSQL přejdi, jakmile začne upravovat obsah několik lidí najednou nebo API dostává skutečný provoz.

Můžu APP_KEYS nebo jiné tajné klíče později změnit?

Můžeš, ale každá změna něco stojí: podle toho, o který klíč jde, se odhlásí administrátoři, přestanou platit API tokeny nebo skončí uživatelské relace. Rotuj uvážlivě, jeden klíč po druhém, a lidi, kteří tokeny drží, předem upozorni.

Musím po změně .env znovu buildovat?

U přihlašovacích údajů k databázi a tajných klíčů ne - čtou se při startu serveru, takže stačí restart. Nový build je potřeba po změně veřejného URL, cesty k administraci, pluginů nebo verze Strapi.

Proč na produkčním serveru nejde vytvářet typy obsahu?

Strapi mimo vývojový režim Content-Type Builder záměrně vypíná. Modely jsou kód a jejich změna na živém serveru by změnila schéma databáze bez jakéhokoli záznamu ve správě verzí.

Zálohuje strapi export i administrátorské účty?

Ne. Administrátorské účty a API tokeny do archivu nepatří. Měj i zálohu na úrovni databáze a export ber jako nástroj pro migraci a přenos.

Pomohlo vám to?