Ir para o conteúdo
Q
QuoteNode

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:

CaminhoQuando usarMecanismo
A — CLI do hostConsegues executar docker no host (Ubuntu VPS, LAN Linux, Compose simples)scripts/migrate-postgres-major.sh
B — stack só-UISó 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 localimagem 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)

  1. Faz backup do .env. Contém segredos que não podem ser regenerados, sobretudo DB_ENCRYPTION_KEY.
  2. 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
  3. Pausa o auto-deploy se a tua plataforma reimplanta num tag móvel (ex.: auto-deploy do Coolify, :latest do Portainer). Vê Ordem de lançamento — uma imagem PG18 não pode chegar antes de a migração correr.
  4. 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.

  1. Encontra o nome do volume de dados:
    docker volume ls | grep postgres
  2. Para o stack (mantém os volumes):
    docker compose down
  3. 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:
    DB_NAME=quotenode DB_USERNAME=quotenode DB_PASSWORD='...' \
    POSTGRES_VOLUME=<your_postgres_volume> \
    DB_MAJOR_MIGRATE_CONFIRM=yes \
      ./scripts/migrate-postgres-major.sh
    O script não faz nada destrutivo sem DB_MAJOR_MIGRATE_CONFIRM=yes e aborta (deixando os teus dados intactos) se encontrar roles/extensions inesperados ou espaço em disco insuficiente.
  4. Arranca o stack normalmente. Sobe em PostgreSQL 18 e aplica quaisquer migrações pendentes:
    docker compose up -d
  5. 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_ARGS correspondente (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 postgres entra 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 postgres para 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 contentor postgres do Coolify; pará-lo não chega.
  • Depois de migrar a base de dados, recupera o stack com «Redeploy» — não «Restart». Como o contentor postgres foi removido, o Coolify tem de o recriar. Clica em Redeploy no recurso; o Coolify arranca então postgres:18-alpine sobre 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:

  1. cold-backup — um tar imutável do volume A (imagem de rollback), tirado antes de algo ler A.
  2. dump — um preflight fail-closed do cluster em execução + um dump lógico.
  3. restore — restauro para um novo volume PostgreSQL 18 B.
  4. verify — verifica a versão principal, o esquema, as extensions e a paridade de locale, e escreve REPORT.txt no volume de artefactos.

Passos

  1. Para o stack da aplicação na UI da tua plataforma (não apagues volumes).

  2. 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.
  3. Implanta o stack de migração com estas variáveis de ambiente:

    VariávelValor
    SOURCE_VOLUME_NAMEo volume existente A do passo 2
    DB_NAME DB_USERNAME DB_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
  4. Lê o relatório. Quando a fase verify terminar, lê REPORT.txt do volume de artefactos (quotenode_pg_upgrade_artifacts). Tem de dizer PASS. 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).

  5. 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 external por 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 por docker-compose.upgrade.yml dentro 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, o PGDATA=/var/lib/postgresql/data legacy, sem healthcheck e restart: no, e fixa a imagem por digest.

Passos

  1. Para o stack e faz uma cópia crua fora do host do volume A — é o teu único rollback:
    docker run --rm -v <your_postgres_volume>:/d:ro -v "$PWD:/out" alpine:3.23 \
      tar czf /out/pg16-volume-backup.tar.gz -C /d .
    Copia pg16-volume-backup.tar.gz para fora do host.
  2. Executa o ficheiro one-shot pronto contra o teu volume em execução (SOURCE_VOLUME_NAME = volume A):
    SOURCE_VOLUME_NAME=<your_postgres_volume> \
    DB_NAME=quotenode DB_USERNAME=quotenode DB_PASSWORD='...' \
      docker compose -f infra/docker-compose.pgautoupgrade.unsafe.yml up
    Espera que o contentor termine com exit 0.
  3. 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
  4. 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).
  5. 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.yml num 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_NAME para B:
    POSTGRES_VOLUME_NAME=quotenode_postgres_upgraded \
      docker compose -f infra/docker-compose.coolify.yml \
                     -f infra/docker-compose.promote.yml up -d
    (usa docker-compose.prod.yml em vez de coolify.yml para o ficheiro prod). Para reverter, remove -f docker-compose.promote.yml e 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ço postgres para o volume B nas definições de storage da plataforma, ou adiciona external: true + name: <B> para o volume do postgres no editor compose do stack.

Verificar

Depois de o stack principal estar a correr em B:

  1. Abre /health — deve devolver UP.
  2. Inicia sessão e confirma que os teus dados existentes (orçamentos, clientes, definições) estão presentes.
  3. Cria um orçamento de teste e gera um PDF.
  4. 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:

  1. Pausa o auto-deploy / fixa o tag de imagem atual.
  2. Executa a migração (caminho A ou B) e promove B.
  3. 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_NAME ou 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.

Last reviewed: Recently