Migrace MyInvoice → MyÚčto: tři zádrhely, na které jsem narazil
MyInvoice, který používám na fakturaci, se přejmenoval na MyÚčto — nový projekt od stejného vývojáře (Radek Hulán), sdílející základ, ale s vestavěnou nadstavbou pro podvojné účetnictví. Fakturační funkce zůstávají navždy zdarma, nadstavba je placená. Rozhodl jsem se přejít, protože veškerý vývoj se teď soustředí tam.
Na papíře je to jednoduché: nasadit vedle staré instalace, spustit vestavěný migrační skript, přepnout konfiguraci nginx. V praxi jsem narazil na tři záludnosti, které stály za zdržením — a asi budou trápit i další, kdo migraci budou dělat.
Příprava — nic překvapivého
- Zálohoval jsem DB dump (
mariadb-dump --all-databases) a appdata volume (faktury, loga) přesdocker runs bind mountem. - Nasadil jsem MyÚčto jako nový, samostatný Docker stack vedle staré instalace — ne upgrade na místě. Nový interní port, vlastní DB kontejner.
- Propojil jsem appku MyÚčto se sítí staré MyInvoice instalace, aby mohla migrační skript dosáhnout na zdrojová data.
A tady začaly komplikace.
Past č. 1: Docker DNS alias kolize
Appka po nastartování hlásila Access denied for user 'myucto'@'IP' (using password: YES) — heslo bylo správné. A přesto access denied.
Vysvětlení: obě Compose sítě (stará MyInvoice i nová MyÚčto) mají stejný service alias db — to je defaultní jméno, které Compose dá kontejneru podle názvu service v docker-compose.yml. Jakmile jsem appku připojil do obou sítí najednou (kvůli přístupu ke zdrojovým datům), Docker embedded DNS matchnul db na tu špatnou síť. Appka se tak snažila přihlásit do MyInvoice databáze s heslem, které platilo pro MyÚčto — logicky nesedělo.
Řešení: nepoužívat generický service alias db, ale jméno kontejneru — to je globálně unikátní napříč celým Dockerem:
db.host => 'myucto-db-1' // místo 'db'
Past č. 2: OPcache, který nevidí změny
I po opravě configu appka dál hlásila stejnou chybu. Config na disku byl správně, appka po include v PHP vracela správnou hodnotu — ale PDO connection string v error logu pořád ukazoval starou hodnotu. Jako by appka žila v jiné realitě než souborový systém.
Příčina: opcache.validate_timestamps => Off. PHP OPcache nikdy nezjistí, že se soubor od zkompilování změnil, a drží starou zkompilovanou verzi v paměti — klidně i přes docker compose restart, protože samotný proces uvnitř kontejneru zůstává živý a jeho paměť s ním.
Řešení: po každé úpravě konfigurace udělat docker compose up -d --force-recreate app, ne jen restart. Parametr force-recreate zabije starý kontejner a vytvoří nový proces od nuly.
Past č. 3: Migrace bez pepperu = neplatné přihlašovací údaje
Appka konečně naběhla, migrace dat proběhla (81 z 86 tabulek napoprvé, zbytek po odstranění --no-truncate, který kolidoval se seed daty). Přihlašuju se — "Neplatné přihlašovací údaje".
Bcrypt hash hesla je nerozlučně svázaný s pepperem (dodatečná sůl navíc k samotnému hashi), který appka používá při ověřování. Migrační skript přenese hashe hesel z DB tak, jak jsou — ale nový MyÚčto config měl nově vygenerovaný pepper, ne ten původní z MyInvoice. Stejná logika platí pro secret_encryption_key, který dešifruje TOTP secrets pro 2FA.
Řešení: pepper i secret_encryption_key musí být identické jako ve zdrojové instalaci — nekopíruje se jen databáze, ale i tyhle dva klíče z cfg.php.
Shrnutí — co bych řekl někomu, kdo to bude dělat po mně
- Zálohuj DB dump i storage volume, než se čehokoliv dotkneš.
- Nasaď novou instanci vedle, ne přes starou.
- Zkopíruj pepper a secret_encryption_key ze zdroje 1:1 — jinak žádné heslo/2FA po migraci nepůjde ověřit.
- Pokud appku propojuješ se sítí staré instalace kvůli zdrojovým datům, adresuj databáze podle jména kontejneru, ne podle service aliasu — jinak riskuješ, že appka mluví s cizí DB.
- Po každé úpravě configu dělej
--force-recreate, ne jen restart — OPcache jinak žije ve své vlastní minulosti. - Migrační skript spouštěj bez
--no-truncatena čerstvě vytvořené prázdné DB, ať si sám vyčistí seed tabulky. - Nezapomeň na
storage/— DB migrace nenese PDF, loga ani přílohy, to je samostatný krok.
Samotná migrace dat proběhla bez ztráty jediného řádku. Většinu času jsem strávil nad debugováním zmíněných komplikací, které nemají nic společného s kvalitou migračního skriptu samotného — jsou to obecné Docker/PHP nástrahy, na které narazí kdokoliv, kdo propojuje dva stacky a mezitím ladí konfiguraci za běhu.
