Installation
Migrar os dados para o PostgreSQL 18
Procedimento único e seguro para os dados para mover um deployment PostgreSQL 16 existente para o PostgreSQL 18 sem perda de dados.
Migrar os dados para o PostgreSQL 18
Quem precisa disto: apenas deployments que já rodam em PostgreSQL 16 e querem passar para uma build do QuoteNode que exige PostgreSQL 18. Instalações novas não precisam de nada — arrancam diretamente no PostgreSQL 18.
Um servidor PostgreSQL 18 não consegue arrancar sobre um diretório de dados do PostgreSQL 16. A aplicação recusa-se a arrancar sobre um diretório incompatível e nunca o reescreve automaticamente, por isso uma instalação existente tem de avançar os seus dados uma vez. É um procedimento de operador deliberado e único.
A migração é segura para os dados por design: o teu volume de dados existente (chamemos-lhe A) nunca é apagado, recriado ou sobrescrito. Os dados são copiados para um novo volume PostgreSQL 18 (B); A permanece intacto como rollback imediato até confirmares que B funciona.
Há três caminhos (o caminho C é um último recurso desaconselhado). Escolhe conforme tenhas ou não shell no host:
| Caminho | Quando usar | Mecanismo |
|---|---|---|
| A — CLI do host | Consegues executar docker no host (Ubuntu VPS, LAN Linux, Compose simples) | scripts/migrate-postgres-major.sh |
| B — stack só-UI | Só tens uma UI (Coolify, Portainer, QNAP, Synology) | infra/docker-compose.upgrade.yml |
| C — auto-upgrade no local ⚠️ | Apenas último recurso; nem A nem B funciona e aceitas o risco no local | imagem pgautoupgrade (não recomendado) |
Os caminhos A e B são seguros para os dados: copiam para um novo volume e mantêm o original intacto. O caminho C atualiza no local — não há rollback intacto, apenas o teu backup fora do host. Prefere A ou B.
Antes de começar (todos os caminhos)
- Faz backup do
.env. Contém segredos que não podem ser regenerados, sobretudoDB_ENCRYPTION_KEY. - Faz um backup da base de dados (além da cópia de segurança automática que a migração faz):
docker compose exec postgres pg_dump -U quotenode quotenode > backup-before-pg18.sql - Pausa o auto-deploy se a tua plataforma reimplanta num tag móvel (ex.: auto-deploy do Coolify,
:latestdo Portainer). Vê Ordem de lançamento — uma imagem PG18 não pode chegar antes de a migração correr. - Para o stack da aplicação (sem apagar volumes). Nunca executes
docker compose down -v.
Caminho A — CLI do host (recomendado quando tens shell)
É o caminho mais simples para Ubuntu VPS, LAN Linux e qualquer host Docker Compose simples.
- Encontra o nome do volume de dados:
docker volume ls | grep postgres - Para o stack (mantém os volumes):
docker compose down - Executa a migração. Captura dois artefactos de segurança (um dump lógico e um tarball cru do volume) e
verifica ambos antes de tocar em algo, executa um preflight fail-closed no cluster em execução, depois
recria o volume em PostgreSQL 18 e restaura:
O script não faz nada destrutivo semDB_NAME=quotenode DB_USERNAME=quotenode DB_PASSWORD='...' \ POSTGRES_VOLUME=<your_postgres_volume> \ DB_MAJOR_MIGRATE_CONFIRM=yes \ ./scripts/migrate-postgres-major.shDB_MAJOR_MIGRATE_CONFIRM=yese aborta (deixando os teus dados intactos) se encontrar roles/extensions inesperados ou espaço em disco insuficiente. - Arranca o stack normalmente. Sobe em PostgreSQL 18 e aplica quaisquer migrações pendentes:
docker compose up -d - Faz a Verificação e depois apaga os artefactos de segurança impressos pelo script quando estiveres confiante.
Locale não predefinido: se a tua base de dados de origem usa um locale diferente do predefinido da imagem, o script falha em fail-closed na verificação de encoding/collation (uma mudança silenciosa de collation pode corromper índices de texto). Reexecuta com um
POSTGRES_INITDB_ARGScorrespondente (ex.:--locale=...).
Notas para o Coolify (hosts geridos)
O caminho A executado por SSH no servidor Coolify é a via mais simples num host Coolify. Algumas particularidades do Coolify, confirmadas na prática:
- Um deployment «Failed» antes de migrar é esperado. Quando o Coolify implanta uma build que exige
PostgreSQL 18 sobre um volume PostgreSQL 16 existente, o contentor
postgresentra em crash-loop (FATAL: database files are incompatible with server) e o Coolify marca o deployment como Failed. Os teus dados não são tocados — o PostgreSQL apenas se recusa a arrancar sobre um diretório de dados mais antigo. - Tens de remover o contentor
postgrespara migrar. O caminho A recria o volume de dados, e o Docker recusa-se a remover um volume enquanto qualquer contentor — mesmo parado — ainda o referencia. Por isso o procedimento remove primeiro o contentorpostgresdo Coolify; pará-lo não chega. - Depois de migrar a base de dados, recupera o stack com «Redeploy» — não «Restart». Como o contentor
postgresfoi removido, o Coolify tem de o recriar. Clica em Redeploy no recurso; o Coolify arranca entãopostgres:18-alpinesobre o volume migrado e recupera a app. - O Coolify reutiliza o volume migrado. Recriar o volume perde as labels Compose do Coolify, mas o Docker Compose (e portanto o redeploy do Coolify) faz a correspondência de volumes por nome e reutiliza o existente — os teus dados ficam. Mantém os artefactos de segurança até teres verificado a app reimplantada.
- Encontra o nome real do volume no separador Storages do recurso; tem prefixo UUID, ex.
<uuid>_postgres-prod-data(repara nos hífenes). Se corres vários stacks (ex.: uma instalação dev e uma main) num host, migra cada um pelo seu próprio nome de volume e não toques no outro.
Caminho B — stack de migração só-UI (Coolify, Portainer, NAS)
Quando não tens shell no host, usa o stack de migração dedicado e temporário
infra/docker-compose.upgrade.yml. Não faz parte de nenhum deployment normal — sobe-o apenas para a
migração e remove-o depois. Usa imagens PostgreSQL padrão, sem socket do Docker, e é controlado inteiramente
pelos entrypoints dos contentores.
Executa estas fases automaticamente e para num relatório verificado — nunca promove sozinho:
- cold-backup — um tar imutável do volume A (imagem de rollback), tirado antes de algo ler A.
- dump — um preflight fail-closed do cluster em execução + um dump lógico.
- restore — restauro para um novo volume PostgreSQL 18 B.
- verify — verifica a versão principal, o esquema, as extensions e a paridade de locale, e escreve
REPORT.txtno volume de artefactos.
Passos
-
Para o stack da aplicação na UI da tua plataforma (não apagues volumes).
-
Encontra o nome do volume de dados existente (é o A, usado como
SOURCE_VOLUME_NAME):- Coolify: o volume tem o prefixo do UUID do recurso, ex.
<uuid>_postgres_prod_data. - Portainer / QNAP / Synology: o prefixo é o nome do stack, ex.
<stack>_postgres_data. - Predefinição do Compose:
<project>_postgres_prod_data.
- Coolify: o volume tem o prefixo do UUID do recurso, ex.
-
Implanta o stack de migração com estas variáveis de ambiente:
Variável Valor SOURCE_VOLUME_NAMEo volume existente A do passo 2 DB_NAMEDB_USERNAMEDB_PASSWORDas tuas credenciais de base de dados existentes TARGET_VOLUME_NAMEopcional; predefinição quotenode_postgres_upgraded(este é o B)# equivalente em Docker puro do que a UI executa: docker compose -f infra/docker-compose.upgrade.yml up -
Lê o relatório. Quando a fase
verifyterminar, lêREPORT.txtdo volume de artefactos (quotenode_pg_upgrade_artifacts). Tem de dizerPASS. Se alguma fase falhar, o stack para em segurança; A fica intacto — corrige a causa e tenta de novo (apaga B primeiro, vê Recuperação). -
Remove o stack de migração (mantém os seus volumes) e depois Promover B.
Visibilidade dos volumes por plataforma (verifica na tua): o caminho B assume que o stack de migração consegue montar o volume A existente da aplicação como volume
externalpor nome. A maioria dos hosts Docker permite-o. Se a tua plataforma isola os volumes por stack/recurso e o stack de migração não vê A, usa a variante no local: substitui temporariamente o compose da aplicação pordocker-compose.upgrade.ymldentro do mesmo recurso/projeto para que herde o mesmo namespace de volumes. Confirma-o na tua plataforma específica antes de confiares nisso em produção.
Caminho C — imagem de auto-upgrade no local (parece o mais simples, mas arriscado — não recomendado) ⚠️
Apenas último recurso. Usa-o só quando nem o caminho A nem o B for viável e aceitares os riscos abaixo. Ao contrário de A e B, este caminho atualiza no local sobre o teu volume A existente, por isso não há volume de rollback intacto — o teu backup fora do host é a única rede de segurança.
A ideia parece a mais simples: deixa uma imagem auto-atualizadora
(pgautoupgrade) executar pg_upgrade uma vez
sobre o teu volume existente e depois arranca o postgres:18-alpine normal sobre ele. Mas não é uma
mudança de tag de uma linha no teu compose normal — o serviço postgres aí tem um override command: (que
suprime totalmente o entrypoint de upgrade), restart: unless-stopped (que poria o one-shot terminado em
loop) e um healthcheck (que marca o servidor em upgrade como unhealthy). Os três quebram um upgrade no local.
O repo inclui um ficheiro one-shot pronto e autónomo que os evita a todos:
infra/docker-compose.pgautoupgrade.unsafe.yml (o .unsafe é deliberado). Factos chave:
pg_upgradeé destrutivo no local — em caso de sucesso o cluster antigo desaparece, e uma falha a meio pode deixar o diretório de dados meio convertido. O teu backup fora do host é o único rollback.- O ficheiro mantém
PGAUTO_ONESHOT=yes, oPGDATA=/var/lib/postgresql/datalegacy, sem healthcheck erestart: no, e fixa a imagem por digest.
Passos
- Para o stack e faz uma cópia crua fora do host do volume A — é o teu único rollback:
Copiadocker run --rm -v <your_postgres_volume>:/d:ro -v "$PWD:/out" alpine:3.23 \ tar czf /out/pg16-volume-backup.tar.gz -C /d .pg16-volume-backup.tar.gzpara fora do host. - Executa o ficheiro one-shot pronto contra o teu volume em execução (
SOURCE_VOLUME_NAME= volume A):
Espera que o contentor termine com exit 0.SOURCE_VOLUME_NAME=<your_postgres_volume> \ DB_NAME=quotenode DB_USERNAME=quotenode DB_PASSWORD='...' \ docker compose -f infra/docker-compose.pgautoupgrade.unsafe.yml up - Verifica o layout — o volume tem agora de ser PostgreSQL 18 no caminho legacy:
docker run --rm -v <your_postgres_volume>:/d:ro alpine:3.23 cat /d/PG_VERSION # -> 18 - Arranca o teu stack normal (
postgres:18-alpine). Usa o mesmo volume, agora atualizado — nenhum passo de promoção é necessário (o upgrade foi no local). - Faz a Verificação. Se algo estiver errado, restaura A a partir de
pg16-volume-backup.tar.gz.
Plataformas só-UI (Coolify/Portainer): cola o conteúdo de
infra/docker-compose.pgautoupgrade.unsafe.ymlnum novo stack temporário, define as variáveis acima, implanta uma vez, confirma que saiu, remove esse stack e depois aponta o teu stack normal para o mesmo volume.
Promover B
Após a verificação, aponta o stack principal para o volume migrado B. Os ficheiros compose base nunca são editados (para que as instalações novas fiquem intactas); a promoção é um override.
- Docker / Compose puro: adiciona o override de promoção e define
POSTGRES_VOLUME_NAMEpara B:
(usaPOSTGRES_VOLUME_NAME=quotenode_postgres_upgraded \ docker compose -f infra/docker-compose.coolify.yml \ -f infra/docker-compose.promote.yml up -ddocker-compose.prod.ymlem vez decoolify.ymlpara o ficheiro prod). Para reverter, remove-f docker-compose.promote.ymle arranca de novo — estás de volta em A. - UI do Coolify / Portainer: na UI não há encadeamento
-f. Em vez disso, remapeia o storage do serviçopostgrespara o volume B nas definições de storage da plataforma, ou adicionaexternal: true+name: <B>para o volume do postgres no editor compose do stack.
Verificar
Depois de o stack principal estar a correr em B:
- Abre
/health— deve devolverUP. - Inicia sessão e confirma que os teus dados existentes (orçamentos, clientes, definições) estão presentes.
- Cria um orçamento de teste e gera um PDF.
- Abre um link público para confirmar o routing.
Só depois disto deves apagar o volume A e os artefactos de migração.
Ordem de lançamento (importante)
Se a tua plataforma implanta automaticamente num tag móvel, uma imagem que exige PG18 pode chegar antes da tua migração, quebrando o stack. Sempre:
- Pausa o auto-deploy / fixa o tag de imagem atual.
- Executa a migração (caminho A ou B) e promove B.
- Só então atualiza a aplicação para a build PostgreSQL 18 e reativa o auto-deploy.
Recuperação
O volume de origem A nunca é modificado, por isso a recuperação é simples:
- Migração interrompida antes da promoção (timeout, redeploy, paragem): apaga o volume B e o volume de artefactos, e recomeça. Não há estado parcial para retomar.
- Problema após a promoção: reverte a promoção (remove o override / limpa
POSTGRES_VOLUME_NAMEou reaponta o storage para A) e arranca o stack — estás de volta em PostgreSQL 16 com todos os dados. - Último recurso: restaura a partir de
backup-before-pg18.sql(vê Atualizar o QuoteNode → Reverter).
Nunca executes docker compose down -v durante este procedimento — apaga volumes.