Implantação com Docker Compose
Esta página descreve a implantação do FoxPlan em um servidor único com Docker Compose, atrás do Traefik, que garante o roteamento e a terminação TLS.
Para um alvo de produção com escalabilidade e alta disponibilidade, veja Implantação no Kubernetes.
Duas versões
| Teste local | Instalação empresarial | |
|---|---|---|
| Para | Avaliar o FoxPlan em uma estação de trabalho | Um servidor compartilhado, a produção |
| Arquivos | um único docker-compose.yml | docker-compose.yml, install.sh e arquivos .env |
| Configuração | Nenhuma | Domínio, TLS, SMTP, segredos |
| Acesso | http://localhost:8090, apenas a partir da estação | https:// o seu domínio |
| E-mails | Capturados por uma caixa de teste integrada | Enviados pelo seu servidor SMTP |
As duas versões usam as mesmas imagens e são instaladas em duas etapas distintas:
- Baixar as imagens: o único momento em que é necessário um acesso à Internet, a partir do próprio servidor ou de uma máquina de transferência;
- Colocação em funcionamento — teste local ou instalação empresarial: a pilha inicia apenas com as imagens já presentes. Nenhum registro é contatado; se faltar uma imagem, nada inicia.
O endereço do registro é comunicado pelo FoxPlan. Informe-o na variável FOXPLAN_REGISTRY, no arquivo .env colocado ao lado do docker-compose.yml — tanto para o teste como para a instalação empresarial. O acesso de leitura é livre: não é necessária nenhuma credencial nem docker login.
| Imagem | Função |
|---|---|
foxplan_prod | Aplicação |
foxplan_prod-docs | Esta documentação |
foxplan_prod-export | Exportação dos planejamentos (PDF, PNG, Excel, iCal) |
foxplan_prod-msp | Importação e exportação MS Project e Primavera |
latest acompanha a última versão publicada; cada versão também tem uma tag imutável, a fixar em produção. As imagens de terceiros (MongoDB, Redis, Traefik…) são baixadas do Docker Hub, não deste registro.
Etapa 1: baixar as imagens
O download das imagens é o único momento em que a pilha precisa acessar a Internet. Assim que as imagens estiverem no servidor, o FoxPlan deixa de contatar qualquer registro: nem na inicialização, nem durante o funcionamento, nem ao reiniciar um contêiner.
Imagens a baixar
As quatro imagens do FoxPlan acima, na versão que você implanta, e as seguintes imagens de terceiros, todas baixadas do Docker Hub:
| Imagem | Teste local | Instalação empresarial |
|---|---|---|
traefik:v3.6.4 | Sim | Sim |
mongo:8.2.7 | Sim | Sim (MONGO_IMAGE) |
redis:7.4 | Sim | Sim (REDIS_IMAGE) |
axllent/mailpit:v1.27 | Sim | Não: os seus e-mails saem pelo seu servidor SMTP |
dxflrs/garage:v2.1.0 | Sim | Somente se você adicionar o Garage — veja Armazenamento de arquivos |
curlimages/curl:8.14.1 | Sim: inicializa o Garage na primeira inicialização | Não |
rclone/rclone:1.68.2 | Não | Somente para fazer backup do Garage |
Com o seu próprio armazenamento S3, não são necessários nem Garage, nem curl, nem rclone. Se você alterar MONGO_IMAGE ou REDIS_IMAGE no .env, baixe a imagem que indicar ali.
Fluxos de rede
Fluxos de saída emitidos pelo motor Docker durante o download, todos em HTTPS (TCP/443):
| Destino | Função | Imagens envolvidas |
|---|---|---|
| Host do registro do FoxPlan | Índice das imagens | foxplan_prod, -docs, -export, -msp |
| Serviço de tokens do registro do FoxPlan | Token de acesso anônimo | idem |
| Armazenamento de objetos do registro do FoxPlan | Camadas das imagens: o registro redireciona os downloads para este armazenamento | idem |
registry-1.docker.io, auth.docker.io | Docker Hub: índice das imagens e token de acesso | Imagens de terceiros |
production.cloudflare.docker.com, production.cloudfront.docker.com | Camadas das imagens do Docker Hub (CDN) | idem |
app.fox-plan.com | Download dos arquivos de implantação desta página | Teste local (curl) |
Se o seu firewall filtra por imagem (proxy de registro, lista de permissões do Harbor, Nexus, Artifactory…), autorize os nomes e versões acima.
Os três hosts do registro do FoxPlan são comunicados a você junto com o seu endereço. Um firewall que só autoriza o domínio do registro deixa passar o índice, mas bloqueia as camadas: o pull fica então travado ou falha por tempo esgotado. Os redirecionamentos para o armazenamento de objetos e para o CDN também devem ser autorizados.
Os fluxos de funcionamento — autoridade de certificação ACME, servidor SMTP, armazenamento S3, provedores SSO — estão descritos na matriz de fluxos de rede.
A partir do servidor
Se o servidor puder acessar os registros durante o download:
FOXPLAN_REGISTRY=<endereço comunicado pelo FoxPlan>
V=latest # em produção: a versão fixada em FOXPLAN_VERSION
IMAGES=(traefik:v3.6.4 mongo:8.2.7 redis:7.4)
for i in foxplan_prod foxplan_prod-docs foxplan_prod-export foxplan_prod-msp; do
IMAGES+=("$FOXPLAN_REGISTRY/$i:$V")
done
# Teste local: adicione a caixa de teste e o armazenamento integrado.
# IMAGES+=(axllent/mailpit:v1.27 dxflrs/garage:v2.1.0 curlimages/curl:8.14.1)
# Instalação empresarial com Garage:
# IMAGES+=(dxflrs/garage:v2.1.0 rclone/rclone:1.68.2)
for i in "${IMAGES[@]}"; do
case "$i" in
*-export:*|*-msp:*) docker pull --platform linux/amd64 "$i" ;;
*) docker pull "$i" ;;
esac || echo "ÉCHEC : $i"
done
As imagens foxplan_prod-export e foxplan_prod-msp existem apenas para amd64: o loop sempre as baixa nessa arquitetura. Num servidor ARM ou num Mac com Apple Silicon, elas rodam em emulação. Uma linha ÉCHEC indica uma imagem não baixada: corrija antes de passar à etapa 2.
Os acessos aos registros podem depois ser fechados: a etapa 2 não precisa mais deles.
A partir de um ambiente independente
Se a sua organização pertence a um setor sensível — defesa, saúde, energia, finanças, administração pública, operador de importância vital — não abra esses fluxos a partir do servidor de produção nem da sua rede interna.
Obtenha as imagens em uma máquina independente do seu sistema de informação (estação ou ambiente de transferência dedicado, sem acesso às suas redes internas), verifique-as e depois faça-as entrar pelo seu circuito habitual: registro interno, ou arquivo transferido em suporte controlado.
# 1. Na máquina de transferência, fora do seu sistema de informação.
# Componha IMAGES exatamente como acima (teste ou empresarial, com ou sem Garage):
# uma imagem esquecida aqui faltará na inicialização.
# --platform: a arquitetura do seu servidor de destino (amd64 na maioria dos casos).
for i in "${IMAGES[@]}"; do docker pull --platform linux/amd64 "$i"; done
docker save -o foxplan-images.tar "${IMAGES[@]}"
sha256sum foxplan-images.tar > foxplan-images.tar.sha256
# 2. Controles conforme a sua política: antivírus, análise de vulnerabilidades
# (Trivy, Grype…), impressões digitais das imagens (docker images --digests).
# 3. No servidor de destino, sem acesso à Internet.
sha256sum -c foxplan-images.tar.sha256
docker load -i foxplan-images.tar
As imagens carregadas mantêm o seu nome original: a etapa 2 encontra-as localmente. Se você usar um registro interno, retague-as para ele (docker tag, docker push), baixe-as a partir do servidor e informe esse registro em FOXPLAN_REGISTRY, MONGO_IMAGE e REDIS_IMAGE. O servidor de produção deixa então de ter qualquer fluxo para a Internet para as suas imagens.
Etapa 2: colocação em funcionamento
As duas versões iniciam com --pull never: o Docker Compose usa as imagens presentes na máquina e não contata nenhum registro. Se faltar alguma, a pilha não inicia e a imagem ausente é indicada: refaça a etapa 1 com a lista correta e depois reinicie.
Sem essa opção, uma imagem ausente seria baixada silenciosamente — ou a inicialização ficaria bloqueada por tempo esgotado, num servidor sem acesso à Internet.
Teste local
Em uma estação com Docker Desktop — ou com Docker e Docker Compose v2 — basta um único arquivo. O único valor a fornecer é o endereço do registro, comunicado pelo FoxPlan:
mkdir foxplan-essai && cd foxplan-essai
curl -O https://app.fox-plan.com/docs/onpremise/essai/docker-compose.yml
echo "FOXPLAN_REGISTRY=<endereço comunicado pelo FoxPlan>" > .env
# Verifica se todas as imagens estão presentes e depois inicia sem baixar nada.
MISSING=$(docker compose config --images | while read -r i; do
docker image inspect "$i" >/dev/null 2>&1 || echo "$i"
done)
if [ -n "$MISSING" ]; then echo "Imagens ausentes, nada foi iniciado:"; echo "$MISSING"
else docker compose up -d --pull never; fi
Sem FOXPLAN_REGISTRY, o docker compose para com uma mensagem que a solicita. A verificação prévia interrompe tudo antes da criação de qualquer contêiner; --pull never sozinho também se recusaria a baixar, mas depois de ter criado parte da pilha.
A inicialização leva cerca de um minuto. Depois:
| Endereço | Conteúdo |
|---|---|
http://localhost:8090 | FoxPlan — na primeira inicialização, a página de diagnóstico e, depois, a criação da sua conta |
http://localhost:8090/mailpit/ | Os e-mails enviados pelo FoxPlan, incluindo o código de inscrição |
http://localhost:8090/docs/ | Esta documentação |
Nenhum e-mail sai da estação: uma caixa de teste, o Mailpit, captura todos eles.
O teste inclui um armazenamento de arquivos, o Garage: documentos dos projetos, anexos dos contratos e fotos de perfil funcionam sem configuração. Num Mac com Apple Silicon, os dois serviços de exportação rodam em emulação amd64: é o previsto, e um pouco mais lento.
Apenas os dois serviços de exportação precisam rodar em amd64. Se as outras imagens foram baixadas ou carregadas em amd64 — por exemplo, a partir de um arquivo preparado para um servidor amd64 —, o MongoDB não inicia: a partir da versão 5, ele exige as instruções AVX, que a emulação não fornece. O log do mongo mostra então "MongoDB 5.0+ requires a CPU with AVX support", a aplicação falha com MongoTimeoutException e http://localhost:8090 responde 502.
Um docker pull sem opções não corrige nada: a imagem amd64 já presente é considerada atualizada. Peça explicitamente a versão arm64 e reinicie:
. ./.env # FOXPLAN_REGISTRY
for i in traefik:v3.6.4 mongo:8.2.7 redis:7.4 \
$FOXPLAN_REGISTRY/foxplan_prod:latest $FOXPLAN_REGISTRY/foxplan_prod-docs:latest; do
docker pull --platform linux/arm64 "$i"
done
docker compose up -d --pull never
Criar a primeira conta
O teste não envia nenhum e-mail real: o código de registro é lido no Mailpit.
- Abra
http://localhost:8090: uma página de diagnóstico é aberta (tela Primeira instalação) e mostra se tudo funciona corretamente, sem pedir nada — veja o que é verificado. - Se tudo responder, clique em Criar a primeira instância com a minha conta e informe um endereço de e-mail cujo domínio exista — o seu endereço profissional, por exemplo; um domínio inventado é recusado. A mensagem fica no Mailpit: nada sai da estação.
- Em outra aba, abra
http://localhost:8090/mailpit/: a mensagem com o código de registro chega lá em poucos segundos. - Volte ao FoxPlan e informe o código. O assistente percorre em seguida o seu perfil, a sua empresa, os módulos e os convites, e por fim pede que você escolha a sua senha.
Esta primeira conta é a administradora da instância. Os convites e as redefinições de senha enviados depois também chegam ao Mailpit.
O teste escuta na porta 8090 e não na 80: o macOS não permite que um usuário sem privilégios de administrador abra a porta 80 apenas no endereço 127.0.0.1, e o Docker não mostra nenhum erro — a página simplesmente não responde.
A porta 8090 já está ocupada? Escolha outra na inicialização:
FOXPLAN_PORT=8091 docker compose up -d --pull never
e depois abra http://localhost:8091.
Para parar tudo: docker compose down (dados conservados), ou docker compose down -v para recomeçar do zero.
Para atualizar o teste, refaça a etapa 1 e depois execute novamente docker compose up -d --pull never. Um docker compose up -d sem opção baixa por si só a versão mais recente das imagens do FoxPlan: prático numa estação conectada, mas a etapa 2 deixa então de ser offline.
Os dados do teste estão vinculados ao nome de projeto foxplan-essai, não à pasta: uma pasta nova retoma o teste existente, com as suas contas. Para recomeçar do zero, use docker compose down -v.
O teste escuta apenas em 127.0.0.1 — só é acessível a partir da própria estação — e usa senhas conhecidas por todos, escritas no arquivo. Não o exponha em uma rede e não insira dados reais nele: para receber usuários, passe para a instalação empresarial descrita abaixo.
Instalação empresarial
Arquivos para baixar:
- docker-compose.yml — a pilha completa. Este arquivo não precisa ser modificado.
- install.sh — prepara a implantação: certificado, configuração do Traefik, controles de coerência; verifica as imagens e depois inicia a pilha
- env.example — as variáveis de infraestrutura, a salvar com o nome
.env - env_application.example — as variáveis da aplicação, a salvar com o nome
.env_application - env_mongo.example e env_redis.example — as credenciais do MongoDB e do Redis, a salvar com os nomes
.env_mongoe.env_redis
Coloque os arquivos em um mesmo diretório. Toda a configuração é feita nos arquivos .env*: o docker-compose.yml permanece intacto, o que torna as atualizações de versão indolores.
O restante desta página trata da instalação empresarial: arquitetura, pré-requisitos, dimensionamento e, por fim, o procedimento de instalação propriamente dito.
Arquitetura
Internet ou rede interna
(DNS: DOMAIN → este servidor)
│ 80 / 443
▼
┌─────────┐ terminação TLS
│ Traefik │ redirecionamento 80 → 443
└────┬────┘
┌──────────────────┼──────────────────┐
▼ ▼ ▼
/ (app:5000) /docs (docs:8080) /gantt-export:3200
product-server docs gantt-export
│ │
├──► mongo (rede interna) └──► gantt-msp (interno)
└──► redis (rede interna)
| Serviço | Função | Indispensável |
|---|---|---|
product-server | Aplicação FoxPlan | Sim |
docs | Esta documentação, servida em /docs | Não |
gantt-export | Exportação dos planejamentos em PDF / PNG / Excel / iCal | Não |
gantt-msp | Importação e exportação MS Project e Primavera | Não |
mongo | Base de dados | Sim |
redis | Cache e sessões compartilhadas | Sim |
| Armazenamento S3 | Arquivos: documentos, anexos, fotos de perfil — o seu ou o Garage, veja Armazenamento de arquivos | Não |
Sem o gantt-export, a aplicação funciona normalmente: apenas as exportações gráficas do Gantt ficam indisponíveis. Sem armazenamento S3, apenas os arquivos ficam indisponíveis.
Pré-requisitos
- Um servidor Linux com Docker e Docker Compose v2;
- Um nome de domínio cujo registro DNS aponte para este servidor;
- As portas 80 e 443 acessíveis pelos seus usuários — nenhuma outra, SSO incluído: veja Portas e SSO;
- As imagens, baixadas na etapa 1: a partir do servidor, com um acesso de saída temporário ao registro do FoxPlan e ao Docker Hub, ou importadas a partir de um ambiente de transferência;
- A sua licença;
- Para os arquivos, um armazenamento compatível com S3 — o seu ou o Garage, veja Armazenamento de arquivos;
- Conforme o modo TLS escolhido, um acesso de saída à autoridade de certificação (veja abaixo).
Portas e SSO
O SSO corporativo não exige nenhuma porta adicional, seja o login padrão com Microsoft ou Google, seja um SSO personalizado. O retorno de um login é levado pelo navegador do usuário para https://SEU_DOMINIO/login/oauth2/code/…, pela mesma porta 443 que o resto da aplicação: o provedor de identidade nunca abre uma conexão para o seu servidor.
| Sentido | Porta | Entre | Função |
|---|---|---|---|
| Entrada | 443 (TCP) | Estações dos usuários → Traefik | A aplicação e o retorno do SSO |
| Entrada | 80 (TCP) | Estações dos usuários → Traefik | Redirecionamento para 443; desafio HTTP-01 no modo acme |
| Saída | 443 (TCP) | Contêiner product-server → provedor de identidade | Troca do token, chaves de assinatura, perfil — domínios na matriz de fluxos |
Não publique a porta 5000 da aplicação: tudo passa pelo Traefik, que termina o TLS. Se o seu provedor de identidade escuta em uma porta diferente de 443 — Keycloak em 8443, por exemplo —, é essa porta que deve ser aberta na saída, do servidor para o provedor.
SSO padrão: Microsoft e Google
Ativado pelas variáveis TENANTID_AZURE, CLIENT_ID_AZURE, GOOGLE_CLIENT_ID… de .env_application. A URL de redirecionamento é construída a partir de BASE_URL:
https://SEU_DOMINIO/login/oauth2/code/azure
https://SEU_DOMINIO/login/oauth2/code/google
Declare-a exatamente igual na aplicação registrada no Microsoft Entra ou no Google. Microsoft e Google só aceitam URLs em https.
SSO personalizado: configurado na aplicação
Okta, Keycloak, Microsoft, Google ou qualquer provedor OIDC declarado em SSO corporativo. A URL de redirecionamento é então reconstruída a partir da requisição recebida: o Traefik transmite à aplicação o endereço de origem — cabeçalhos X-Forwarded-Proto, X-Forwarded-Host e X-Forwarded-Port — e o FoxPlan deduz dele https://SEU_DOMINIO/login/oauth2/code/<identificador>. O botão Verificar a configuração mostra a URL exata a declarar.
O Traefik substitui os cabeçalhos X-Forwarded-* pelo que ele próprio vê. Se os usuários acessam o FoxPlan por outra porta pública (:8443), ou se um equipamento colocado à frente do Traefik termina o TLS e lhe transmite HTTP, o FoxPlan calcula uma URL de redirecionamento diferente da do navegador: o provedor recusa o login (redirect_uri mismatch). Um balanceador colocado à frente do Traefik deve, portanto, transmitir o tráfego em HTTPS na porta 443, ou em TCP sem descriptografá-lo, mantendo o nome de domínio de origem.
Dimensionamento
Os valores abaixo ficam um pouco acima do que o FoxPlan mede em funcionamento, para manter margem nos componentes principais.
Para o teste local
| Recurso | Recomendado |
|---|---|
| Processador | 4 vCPU |
| Memória alocada ao Docker | 8 GB (6 GB no mínimo) |
| Disco | 15 GB livres (imagens incluídas) |
Em repouso, o teste consome cerca de 1,5 GB, dos quais 1,1 GB para a aplicação. No Docker Desktop, a memória alocada é ajustada em Settings → Resources. O teste não define nenhum limite por contêiner: adapta-se à memória atribuída ao Docker.
Para a instalação empresarial
| Usuários | Processador | Memória | Disco (SSD) |
|---|---|---|---|
| Até 100 | 4 vCPU | 16 GB | 100 GB |
| De 100 a 500 | 8 vCPU | 32 GB, com 2 instâncias da aplicação | 200 GB |
| Acima disso | Veja Implantação no Kubernetes |
O disco inclui a base de dados, os arquivos armazenados no Garage se ele estiver no mesmo servidor, e espaço para os backups locais antes da sua transferência. Para colocar a base em um disco dedicado, veja Volumes e discos.
Limites de memória dos contêineres
O docker-compose.yml empresarial define um limite de memória por contêiner. Sem limite, a JVM da aplicação e o MongoDB dimensionam-se cada um com base em toda a memória do servidor — um quarto para a JVM, metade para o cache do MongoDB — e acabam por disputá-la. Com um limite, cada um ajusta-se ao seu.
| Contêiner | Limite padrão | Variável (.env) |
|---|---|---|
product-server (aplicação) | 6 GB | FOXPLAN_MEM_APP |
mongo | 4 GB | FOXPLAN_MEM_MONGO |
gantt-export | 2 GB | FOXPLAN_MEM_EXPORT |
redis | 1 GB | FOXPLAN_MEM_REDIS |
gantt-msp | 512 MB | FOXPLAN_MEM_MSP |
docs | 256 MB | FOXPLAN_MEM_DOCS |
traefik | 256 MB | FOXPLAN_MEM_TRAEFIK |
Ou seja, 14 GB para um servidor de 16 GB: sobra o suficiente para o sistema operativo. Em um servidor de 32 GB, duplique FOXPLAN_MEM_APP e FOXPLAN_MEM_MONGO. A aplicação usa 75 % do seu limite para o heap Java; o MongoDB reserva automaticamente metade do seu, menos 1 GB, para o cache.
O processador não é limitado: a inicialização da aplicação e a geração das exportações beneficiam disso, e um limite as tornaria mais lentas sem proteger nada.
Várias instâncias da aplicação
Para um grande número de usuários, execute várias instâncias da aplicação no mesmo servidor. O Traefik as detecta e distribui as requisições entre elas; as sessões são compartilhadas no Redis, de modo que um usuário pode passar de uma instância para outra sem ser desconectado. As tarefas agendadas (relatórios, rotinas, sincronizações) obtêm um bloqueio no Redis: uma única instância as executa, seja qual for o número delas.
No .env:
FOXPLAN_APP_REPLICAS=2
e depois docker compose up -d. Cada instância tem o seu próprio limite FOXPLAN_MEM_APP: a memória necessária aumenta na mesma proporção. Exemplo para um servidor de 32 GB com 2 instâncias:
FOXPLAN_APP_REPLICAS=2
FOXPLAN_MEM_APP=6g # por instância: 12 GB no total
FOXPLAN_MEM_MONGO=8g
Ou seja, cerca de 24 GB de limites no total com os outros contêineres: ainda sobra margem para o sistema.
Quando adicionar uma instância? Quando o processador do contêiner da aplicação permanece saturado nos horários de pico (docker stats), na prática acima de uma centena de usuários ativos ao mesmo tempo. Uma segunda instância também mantém o serviço disponível se uma delas reiniciar.
Duas limitações, próprias de um servidor único:
- uma atualização de versão recria todas as instâncias ao mesmo tempo: a interrupção é curta, mas existe;
- o MongoDB continua sendo um nó único, no mesmo servidor.
Acima de 500 usuários, ou para uma alta disponibilidade real (vários servidores, implantação progressiva sem interrupção), passe para a Implantação no Kubernetes.
Volumes e discos
A base MongoDB e os arquivos armazenados no Garage ficam em diretórios do host, nunca em volumes Docker. Um volume Docker desaparece com docker compose down -v ou um docker system prune --volumes; um diretório do host, não. Colocado em um disco dedicado, ele sobrevive também a uma reinstalação do Docker ou do sistema.
| Dados | No contêiner | Local no host | Vari ável (.env) |
|---|---|---|---|
| Base MongoDB | /data/db | Diretório, obrigatório | FOXPLAN_MONGO_DATA |
| Arquivos no Garage (opcional) | /var/lib/garage/meta, /var/lib/garage/data | Diretório, subpastas meta e data | FOXPLAN_GARAGE_DATA |
| Redis (cache, sessões) | /data | Volume Docker redis_data, ou um diretório | FOXPLAN_REDIS_DATA (opcional) |
| Certificados do Traefik | /certs, /acme | Pasta ./traefik/ da instalação | — |
Sem FOXPLAN_MONGO_DATA, o docker compose se recusa a iniciar, com uma mensagem que a solicita. O env.example propõe ./data/mongo, na pasta de instalação: isso serve para um piloto. Em produção, coloque a base em um disco dedicado, como descrito abaixo. O Redis pode ficar em um volume Docker: ele contém apenas o cache e as sessões, que se reconstroem sozinhos (se forem perdidos, apenas desconectam os usuários).
Montar um disco dedicado para a base
Os comandos a seguir são executados no servidor, como administrador, antes da primeira inicialização do FoxPlan. Eles pressupõem um disco adicional vazio, em SSD; um volume de storage (SAN via iSCSI ou Fibre Channel) também serve. Evite compartilhamentos de rede NFS ou SMB: neles o MongoDB perde desempenho e confiabilidade.
1. Identificar o disco. Ele aparece sem partição nem ponto de montagem, por exemplo sdb:
lsblk -f
2. Formatá-lo em XFS, o sistema de arquivos recomendado pelo MongoDB (pacote xfsprogs). Este comando apaga o disco: verifique duas vezes o seu nome.
sudo mkfs.xfs -L foxplan-data /dev/sdb
3. Montá-lo de forma permanente, pelo seu identificador (UUID), que não muda se a ordem dos discos mudar na reinicialização:
sudo mkdir -p /srv/foxplan
UUID=$(sudo blkid -s UUID -o value /dev/sdb)
echo "UUID=$UUID /srv/foxplan xfs defaults,noatime 0 2" | sudo tee -a /etc/fstab
sudo mount -a
findmnt /srv/foxplan # deve exibir /dev/sdb e xfs
4. Fazer o Docker esperar que o disco esteja montado. Sem essa precaução, se o disco não estiver montado na inicialização do servidor, /srv/foxplan/mongo é apenas uma pasta vazia do disco do sistema: o MongoDB inicia ali uma base vazia, e o FoxPlan parece ter perdido tudo.
sudo mkdir -p /etc/systemd/system/docker.service.d
printf '[Unit]\nRequiresMountsFor=/srv/foxplan\n' | sudo tee /etc/systemd/system/docker.service.d/foxplan-data.conf
sudo systemctl daemon-reload
O Docker deixa então de iniciar enquanto /srv/foxplan não estiver montado.
5. Indicar os diretórios no .env:
FOXPLAN_MONGO_DATA=/srv/foxplan/mongo
FOXPLAN_GARAGE_DATA=/srv/foxplan/garage # somente se você adicionar o Garage
6. Preparar e iniciar. O ./install.sh cria os diretórios (com meta e data para o Garage); a imagem do MongoDB ajusta ela mesma as suas permissões.
./install.sh
docker compose up -d
7. Verificar que a base grava de fato no disco dedicado:
docker inspect foxplan-mongo --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{println}}{{end}}'
# /srv/foxplan/mongo -> /data/db
sudo ls /srv/foxplan/mongo # arquivos WiredTiger*, collection-*…
df -h /srv/foxplan # espaço livre do disco de dados
Monitore em seguida o espaço livre: o MongoDB sem espaço para de repente, e a aplicação junto com ele.
Instalação existente: tirar a base de um volume Docker
As instalações anteriores guardavam a base no volume Docker mongo_data. Após atualizar o docker-compose.yml, defina FOXPLAN_MONGO_DATA e execute ./install.sh: se ele encontrar o diretório vazio enquanto existe uma base no volume antigo, ele para e exibe o comando de cópia a executar. Em detalhe:
docker compose stop product-server mongo
mkdir -p /srv/foxplan/mongo
docker run --rm -v SEU_PROJETO_mongo_data:/from:ro -v /srv/foxplan/mongo:/to alpine \
sh -c 'cp -a /from/. /to/'
./install.sh
docker compose up -d
O nome exato do volume é obtido com docker volume ls | grep mongo_data. Mantenha o volume antigo até ter verificado a aplicação e depois exclua-o (docker volume rm). Mesmo princípio para o Garage: copie garage_meta para $FOXPLAN_GARAGE_DATA/meta e garage_data para $FOXPLAN_GARAGE_DATA/data.
Instalação
1. Preparação
chmod +x install.sh
./install.sh
Na primeira execução, o script cria o .env a partir do env.example e para, para que você o preencha.
| Variável | Descrição |
|---|---|
DOMAIN | Domínio servido, por exemplo foxplan.minha-empresa.pt |
FOXPLAN_REGISTRY | Endereço do registro de imagens, comunicado pelo FoxPlan |
FOXPLAN_IMAGE | Nome base das imagens — já preenchido: foxplan_prod |
FOXPLAN_VERSION | Versão implantada — fixe uma versão precisa em produção |
MONGO_IMAGE / REDIS_IMAGE | Versões validadas das dependências |
FOXPLAN_MONGO_DATA | Diretório da base no host — de preferência um disco dedicado, veja Volumes e discos |
TLS_MODE | selfsigned, acme ou static — veja TLS |
ACME_EMAIL, ACME_CA_SERVER | Autoridade de certificação, se TLS_MODE=acme |
latest em produçãoFOXPLAN_VERSION=latest acompanha a última versão publicada: prático para um piloto, desaconselhado depois. Uma simples reinicialização de contêiner pode então mudar de versão sem que você o tenha decidido. Fixe uma versão e faça as suas atualizações de forma explícita.
Execute depois novamente o ./install.sh: ele gera o certificado padrão e a configuração do Traefik correspondente ao seu TLS_MODE. O script é idempotente — você pode executá-lo novamente quantas vezes quiser, ele nunca substitui um certificado nem um .env existentes.
2. Segredos da aplicação
Três arquivos, a criar a partir dos modelos baixados acima:
cp env_application.example .env_application # base de dados, e-mails, S3, JWT…
cp env_mongo.example .env_mongo # credenciais do MongoDB
cp env_redis.example .env_redis # senha do Redis
O SECRET_KEY_JWT é gerado de uma vez por todas:
openssl rand -base64 64 | tr -d '\n'
Coerências a respeitar, sob pena de falha na inicialização:
URL_MONGOretoma o usuário e a senha de.env_mongo, com o hostmongo;SPRING_REDIS_PASSWORDé idêntico aREDIS_PASSWORD;BASE_URLvalehttps://seguido deDOMAIN— é ele que constrói os links contidos nos e-mails.
O .env é lido pelo Docker Compose para montar a pilha; ele não é transmitido ao contêiner da aplicação. As variáveis da aplicação — base de dados, e-mails, S3, JWT — vão no .env_application. Uma variável da aplicação colocada no .env fica sem efeito, silenciosamente.
Envio de e-mails
O FoxPlan envia convites, redefinições de senha e notificações. Sem configuração, nenhum e-mail é enviado: a aplicação inicia e funciona, mas essas funções permanecem mudas.
A preencher no .env_application:
MAIL_PROXY_TYPE=smtp
MAIL_FROM=foxplan@mon-entreprise.fr
MAIL_SMTP_HOST=smtp.mon-entreprise.fr
MAIL_SMTP_PORT=587
MAIL_SMTP_USERNAME=
MAIL_SMTP_PASSWORD=
MAIL_SMTP_AUTH=true
MAIL_SMTP_STARTTLS=true
A aba Configurações → Empresa → E-mails mostra, e-mail por e-mail, o que o seu servidor de envio respondeu, com um banner verde / laranja / vermelho sobre o estado da plataforma. É o primeiro lugar a consultar quando um usuário diz não ter recebido nada — veja Registro de e-mails.
MAIL_PROXY_TYPE também aceita sendgrid ou gmail, para uma conta fornecida por você; MAIL_PROXY_PARAMS contém então <remetente>;<chave ou senha>. Na grande maioria das instalações, smtp é a escolha certa.
Dois pontos decidem o destino das suas mensagens:
MAIL_FROMdeve estar autorizado a enviar pelo seu domínio (SPF, DKIM). Um remetente não alinhado vai parar no spam, ou é rejeitado sem que a aplicação saiba de nada.BASE_URLconstrói os links dos e-mails. Se estiver ausente ou incorreto, as mensagens são enviadas corretamente, mas os links que elas contêm — convite, redefinição — não levam a lugar nenhum.
Para verificar após a inicialização, acione uma redefinição de senha a partir da tela de login e acompanhe os logs:
docker compose logs -f product-server | grep -i mail
Uma falha de envio aparece ali como aviso: a aplicação nunca é interrompida por causa de um e-mail não enviado.
3. Inicialização
./install.sh --start
O script verifica primeiro se todas as imagens da pilha estão presentes no servidor. Se faltar alguma, ele a indica e para sem iniciar nada: baixe-a (etapa 1) e depois reinicie. Caso contrário, inicia a pilha com docker compose up -d --pull never, sem contatar nenhum registro.
Verificação:
docker compose ps
curl -I https://SEU_DOMINIO/
curl -I https://SEU_DOMINIO/docs/
Uma vez conectado como administrador, a aba Configurações → Conta FoxPlan → Diagnóstico testa a base de dados, o Redis, o envio de e-mails (uma mensagem de teste é enviada para você), o armazenamento S3 e o serviço de exportação — veja Conta FoxPlan.
4. Criar a primeira conta
Abra https://SEU_DOMINIO: enquanto nenhuma instância existir, uma página de diagnóstico é aberta (tela Primeira instalação) e mostra se tudo funciona corretamente — veja o que é verificado. Se tudo responder, clique em Criar a primeira instância com a minha conta. O FoxPlan envia um código de registro para o endereço informado, pelo servidor SMTP configurado em .env_application: ele chega à caixa de entrada real do destinatário. Informe o código, siga o assistente (perfil, empresa, módulos, convites) e escolha em seguida a sua senha.
Esta primeira conta é a administradora da instância: use um endereço da equipe que vai operar o FoxPlan, não uma conta pessoal provisória.
Sem um servidor SMTP funcional, o código de registro não é enviado e nenhuma conta pode ser criada. É por isso que a tela de primeira instalação só propõe a criação depois de o servidor de envio ser alcançado. Verifique a seção Envio de e-mails e acompanhe os logs durante o cadastro: docker compose logs -f product-server | grep -i mail.
5. Verificar a instalação: diagnóstico
O diagnóstico verifica, tanto para o teste como para a instalação empresarial:
| Elemento | Verificação |
|---|---|
| Aplicação | O servidor FoxPlan responde |
| Base de dados | O MongoDB responde |
| Cache | O Redis responde |
| Servidor de envio de e-mails | O servidor SMTP está acessível (nenhum e-mail é enviado) |
| Armazenamento dos anexos | O bucket S3 está acessível — «não configurado» se não houver nenhum |
| Exportação dos planejamentos | O serviço gantt-export responde, testado a partir do seu navegador |
| Documentação | O serviço docs responde, testado a partir do seu navegador |
Ele pode ser aberto de duas formas:
- Na primeira inicialização, enquanto nenhuma instância tiver sido criada: a tela Primeira instalação executa-o sozinha e depois propõe criar a primeira instância se nenhum elemento estiver em erro. Um elemento «não configurado», como o armazenamento no teste, não bloqueia.
- Depois, a qualquer momento: o ícone de diagnóstico, à direita do seletor de idioma na página de login. Ele só aparece em uma instalação on-premise e funciona sem estar conectado — justamente quando ninguém consegue se conectar.
Sem conexão, o diagnóstico mostra apenas estados, sem nome de servidor nem mensagem técnica: a página de login é visível para todos que a acessam. O detalhe de cada erro, e um e-mail de teste real, estão no diagnóstico completo, depois de conectado: Configurações → Conta FoxPlan → Diagnóstico — veja Conta FoxPlan.
Armazenamento de arquivos
O FoxPlan guarda os seus arquivos em um armazenamento de objetos compatível com S3: documentos dos projetos, anexos dos contratos de clientes, fotos de perfil. Os arquivos permanecem privados: a aplicação lê-os e serve-os ela própria, após verificar os direitos. O armazenamento não precisa, portanto, de estar acessível a partir da Internet, nem de gerir permissões públicas.
Variável (.env_application) | Função |
|---|---|
FILE_S3_URL | Endereço do serviço S3, por exemplo http://garage:3900 |
FILE_S3_BUCKET | Bucket dos arquivos |
FILE_S3_ACCESS_KEY, FILE_S3_SECRET_KEY | Credenciais de acesso ao bucket |
FILE_S3_REGION | Região assinada em cada pedido — a que o seu armazenamento espera. Padrão: fr-par |
Sem armazenamento, deixe estas variáveis vazias: a aplicação funciona, apenas os arquivos ficam indisponíveis. Um arquivo não pode ultrapassar 10 MB. O FoxPlan não aplica nenhuma quota on-premise: o espaço é seu.
O seu armazenamento existente
Qualquer armazenamento compatível com S3 serve: Ceph, NetApp StorageGRID, Scality, MinIO, AWS… Crie um bucket e credenciais dedicados ao FoxPlan, com direitos de leitura, escrita e eliminação nesse bucket, e depois preencha as variáveis acima.
docker-compose.ymlA instalação empresarial não inclui o Garage: ela se conecta ao armazenamento que você indicar. Deixe o docker-compose.yml como está e não crie um docker-compose.override.yml; toda a configuração vai em .env_application:
# .env_application — o seu armazenamento S3
FILE_S3_URL=https://s3.minha-empresa.local # endereço do serviço S3, sem o nome do bucket
FILE_S3_BUCKET=foxplan # bucket criado para o FoxPlan
FILE_S3_ACCESS_KEY=<identificador de acesso>
FILE_S3_SECRET_KEY=<chave secreta>
FILE_S3_REGION=<região esperada pelo armazenamento> # ex. us-east-1, eu-west-3, ou a do seu Ceph/MinIO
Em seguida, aplique a configuração: docker compose up -d product-server (o contêiner é recriado com os novos valores).
- O servidor Docker deve alcançar o endereço
FILE_S3_URL(DNS, firewall): é a aplicação que lê e grava os arquivos, nunca o navegador. - Em
https, o certificado do armazenamento deve ser assinado por uma autoridade reconhecida; um certificado interno não reconhecido faz a conexão falhar. - A aplicação endereça o bucket no caminho (
https://s3.minha-empresa.local/foxplan/…): não é necessário um DNS por bucket. - Verifique o resultado no diagnóstico: a linha Armazenamento deve estar verde.
A região faz parte da assinatura de cada pedido. Se FILE_S3_REGION for diferente da esperada pelo armazenamento, todos os pedidos são recusados (erro 400 nos logs). O diagnóstico assinala-o: armazenamento em erro.
Garage, se não tiver um
O Garage é um armazenamento S3 leve e de código aberto, desenvolvido em França. O teste local já o inclui. Para adicioná-lo à instalação empresarial sem mexer no docker-compose.yml, crie ao lado um arquivo docker-compose.override.yml, que o Docker Compose lê automaticamente:
services:
garage:
image: dxflrs/garage:v2.1.0
restart: always
volumes:
- ./garage.toml:/etc/garage.toml:ro
# Diretórios do host (FOXPLAN_GARAGE_DATA no .env): eles sobrevivem a "docker compose down -v".
- ${FOXPLAN_GARAGE_DATA:?Defina FOXPLAN_GARAGE_DATA no .env}/meta:/var/lib/garage/meta
- ${FOXPLAN_GARAGE_DATA:?Defina FOXPLAN_GARAGE_DATA no .env}/data:/var/lib/garage/data
networks:
- foxplan-network
deploy:
resources:
limits:
memory: 1g
Indique no .env o diretório dos arquivos, de preferência no disco de dados (veja Volumes e discos), e depois execute novamente ./install.sh, que cria as suas subpastas meta e data:
FOXPLAN_GARAGE_DATA=/srv/foxplan/garage
Crie depois o arquivo garage.toml, com dois segredos gerados para a sua instalação:
RPC_SECRET=$(openssl rand -hex 32)
ADMIN_TOKEN=$(openssl rand -base64 32)
printf '%s\n' \
'metadata_dir = "/var/lib/garage/meta"' \
'data_dir = "/var/lib/garage/data"' \
'db_engine = "sqlite"' \
'replication_factor = 1' \
'rpc_bind_addr = "[::]:3901"' \
'rpc_public_addr = "127.0.0.1:3901"' \
"rpc_secret = \"$RPC_SECRET\"" \
'' \
'[s3_api]' \
's3_region = "garage"' \
'api_bind_addr = "[::]:3900"' \
'' \
'[admin]' \
'api_bind_addr = "[::]:3903"' \
"admin_token = \"$ADMIN_TOKEN\"" > garage.toml
chmod 600 garage.toml
Inicie o Garage e prepare-o — topologia, bucket e chave de acesso:
docker compose up -d garage
docker compose exec garage /garage status # anote o ID do nó (primeira coluna)
docker compose exec garage /garage layout assign -z dc1 -c 100G <ID_DO_NO>
docker compose exec garage /garage layout apply --version 1
docker compose exec garage /garage bucket create foxplan
docker compose exec garage /garage key create foxplan # mostra «Key ID» e «Secret key»
docker compose exec garage /garage bucket allow --read --write --owner foxplan --key foxplan
-c 100G é a capacidade anunciada do nó: ajuste-a ao espaço em disco reservado aos arquivos. Por fim, copie a chave para o .env_application:
FILE_S3_URL=http://garage:3900
FILE_S3_BUCKET=foxplan
FILE_S3_ACCESS_KEY=<Key ID>
FILE_S3_SECRET_KEY=<Secret key>
FILE_S3_REGION=garage
e depois reinicie a aplicação:
docker compose up -d product-server
O diagnóstico (ícone da página de login, ou Conta FoxPlan → Diagnóstico) deve mostrar o armazenamento como OK. O Garage só é acessível a partir da rede interna do compose: nenhuma porta é publicada.
Em um arquivo YAML, um valor composto apenas por algarismos é lido como um número: uma chave 0000…01 passaria a 1. Se colocar uma chave em um arquivo docker-compose, ponha-a entre aspas. No .env_application, o problema não se coloca.
Certificados de entrada e de saída
O FoxPlan usa dois certificados diferentes, um por sentido de circulação. Eles não vão nem para o mesmo lugar nem para o mesmo componente.
Fluxos de ENTRADA navegador ──HTTPS──► Traefik ──► aplicação
o Traefik apresenta o certificado DO FOXPLAN → traefik/certs/
Fluxos de SAÍDA aplicação ──HTTPS, SMTP TLS──► AD FS, SMTP, S3 internos…
a aplicação verifica o certificado DELES graças à → ca-sortant/
autoridade que os assinou
| Fluxos de entrada | Fluxos de saída | |
|---|---|---|
| Função | Provar aos navegadores que o servidor é mesmo DOMAIN | Permitir que a aplicação reconheça os seus serviços internos |
| Usado por | Traefik | A aplicação (Java) |
| Conteúdo | O certificado do servidor FoxPlan e a sua chave privada | O certificado público da autoridade — raiz e intermediários, se houver. Nunca uma chave privada |
| Local | traefik/certs/tls.crt e traefik/certs/tls.key | ca-sortant/<nome>.crt — um ou vários arquivos; outro diretório possível com FOXPLAN_OUTBOUND_CA_DIR em .env |
| Formato | PEM. tls.crt: certificado do servidor seguido dos intermediários. tls.key: chave não criptografada, sem frase secreta | PEM, extensão .crt obrigatória. Um arquivo pode conter vários certificados |
| Obrigatório | Sim — o install.sh gera um autoassinado enquanto o seu não chega | Somente se um serviço chamado for assinado por uma autoridade não pública |
| Aplicação | A quente, após touch traefik/dynamic/tls.yml — veja o modo static | Na inicialização: ./install.sh (verifica os arquivos), depois docker compose restart product-server |
Um arquivo PEM é um arquivo de texto que só contém blocos -----BEGIN CERTIFICATE----- … -----END CERTIFICATE----- (e, para a chave, -----BEGIN PRIVATE KEY-----).
É o mesmo certificado?
Não. O certificado de entrada é o do FoxPlan, acompanhado da sua chave privada. O certificado de saída é o da autoridade que assinou os certificados do AD FS, do SMTP ou do S3: público, sem chave.
Por outro lado, eles costumam vir da mesma PKI. Se a autoridade interna assinou tanto o certificado do FoxPlan quanto o do AD FS, o certificado da sua autoridade raiz serve:
- em
ca-sortant/, para que a aplicação reconheça o AD FS, o SMTP…; - em
traefik/ca/, somente no modoacmecom uma PKI interna, para que o Traefik reconheça o servidor ACME.
Nunca copie tls.crt nem tls.key para ca-sortant/: o install.sh recusa qualquer arquivo que contenha uma chave privada.
É necessária uma autoridade de saída?
A partir do servidor FoxPlan, exiba o emissor do certificado de cada serviço interno:
# AD FS, S3, APIs: HTTPS
echo | openssl s_client -connect adfs.minha-empresa.local:443 -servername adfs.minha-empresa.local 2>/dev/null \
| openssl x509 -noout -issuer
# SMTP com STARTTLS
echo | openssl s_client -connect smtp.minha-empresa.local:587 -starttls smtp 2>/dev/null \
| openssl x509 -noout -issuer
Emissor público (DigiCert, Sectigo, GlobalSign, Let's Encrypt…): nada a fazer. Emissor interno: peça à equipe de PKI o certificado da autoridade raiz e dos seus intermediários, no formato PEM, e deposite-o em ca-sortant/.
Se ele faltar, o login SSO falha no retorno do provedor, ou os e-mails não saem, com PKIX path building failed em docker compose logs product-server.
Converter para o formato PEM
| Arquivo recebido | Comando |
|---|---|
.cer ou .der binário | openssl x509 -inform DER -in autoridade.cer -out ca-sortant/autoridade.crt |
.p7b (cadeia de autoridades) | openssl pkcs7 -print_certs -in cadeia.p7b | sed -n '/-----BEGIN/,/-----END/p' > ca-sortant/autoridade.crt |
.pfx ou .p12 (certificado do FoxPlan com a sua chave) | openssl pkcs12 -in foxplan.pfx -nokeys | sed -n '/-----BEGIN/,/-----END/p' > traefik/certs/tls.crtopenssl pkcs12 -in foxplan.pfx -nocerts -nodes | sed -n '/-----BEGIN/,/-----END/p' > traefik/certs/tls.key |
O sed remove as linhas descritivas (Bag Attributes, subject=…) que o openssl acrescenta. Em ca-sortant/, elas impediriam a aplicação de iniciar: o install.sh as detecta e para, indicando o comando de limpeza. Com um .pfx, verifique se tls.crt começa pelo certificado do servidor, antes dos intermediários.
TLS: três modos
Qualquer que seja o modo, o install.sh instala um certificado padrão. O Traefik o serve desde a primeira inicialização e continua a servi-lo se a obtenção automática falhar: o site nunca cai por falta de certificado; na pior das hipóteses, degrada-se para um aviso do navegador.
A escolha é feita pela variável TLS_MODE e depende de uma única pergunta: quem emite os seus certificados, e o Traefik consegue comunicar com ele?
TLS_MODE | Autoridade | Acesso de saída | Renovação | Uso |
|---|---|---|---|---|
selfsigned | Nenhuma | Nenhum | — | Avaliação, piloto |
acme | Let's Encrypt ou a sua PKI | Para a autoridade | Automática | Produção |
static | Qualquer | Nenhum | Por sua conta | Rede fechada, wildcard corporativo |
selfsigned — iniciar imediatamente
Modo padrão. O install.sh gera um certificado autoassinado para o seu DOMAIN, válido por 825 dias. A aplicação fica acessível em HTTPS imediatamente, sem DNS público nem abertura de firewall.
Os navegadores exibirão um aviso: é o esperado. Este modo serve para validar a instalação, não para expor o serviço aos usuários.
acme — renovação automática
TLS_MODE=acme
ACME_EMAIL=admin@mon-entreprise.fr
ACME_CA_SERVER=https://acme-v02.api.letsencrypt.org/directory
O Traefik obtém o certificado através de um challenge HTTP-01 e renova-o sozinho. Com o Let's Encrypt, há duas condições: o domínio deve resolver publicamente para este servidor, e a porta 80 deve estar acessível a partir da Internet — o challenge passa por ela, mesmo que todo o tráfego seja depois redirecionado para a 443.
Com a sua PKI interna, nada muda além do endereço. Se a sua autoridade expõe um serviço ACME — step-ca, HashiCorp Vault, EJBCA — o protocolo é o mesmo (RFC 8555):
ACME_CA_SERVER=https://ca.interne.mon-entreprise.fr/acme/acme/directory
Se essa autoridade for privada, o Traefik não a conhece e a negociação falharia por um certificado desconhecido. Forneça-lhe a cadeia de confiança:
cp /caminho/para/ca-interne.crt traefik/ca/ca-interne.crt
e depois, no .env:
LEGO_CA_CERTIFICATES=/ca/ca-interne.crt
O install.sh verifica a presença do arquivo e avisa você se ele estiver faltando.
É o modo a privilegiar em uma rede fechada: você mantém a renovação automática sem nenhuma dependência da Internet, com a única condição de que o servidor alcance a sua autoridade.
As PKI internas emitem frequentemente certificados válidos por poucos dias. Isso não tem consequências: o Traefik simplesmente renova com mais frequência.
static — o seu certificado, a sua renovação
Para um certificado obtido offline — wildcard corporativo, certificado comercial, autoridade interna sem serviço ACME. Nenhum ACME é configurado: o Traefik nunca sai para a rede para obter um certificado.
Basta sobrescrever os arquivos gerados com os seus:
cp seu-certificado.crt traefik/certs/tls.crt # cadeia completa
cp sua-chave.key traefik/certs/tls.key
chmod 600 traefik/certs/tls.key
tls.crt deve conter o certificado do servidor seguido dos seus intermediários, nesta ordem. Um certificado servido sem a sua cadeia funciona em um navegador desktop — que muitas vezes a completa sozinho — mas falha em dispositivos móveis, na linha de comando e nas chamadas de API. Sintoma clássico e desconcertante: «na minha máquina funciona».
A renovação fica por sua conta. O Traefik não monitora os arquivos de certificado, apenas a configuração dele em traefik/dynamic/: depois de substituir os dois arquivos, toque nessa configuração para que ele os releia a quente, sem reinicialização nem interrupção.
touch traefik/dynamic/tls.yml
Caso contrário, após a substituição o Traefik continua servindo o certificado antigo até a próxima reinicialização — ou até que expire. Se a sua PKI deposita os certificados renovados em um diretório, a tarefa que os copia para traefik/certs/ deve terminar com este touch.
Mudar de modo
Altere TLS_MODE no .env, execute novamente o ./install.sh e depois:
docker compose up -d traefik
O certificado existente é conservado: para recomeçar com um certificado novo, apague traefik/certs/ antes de executar novamente o script.
Verificar o certificado servido
echo | openssl s_client -connect SEU_DOMINIO:443 -servername SEU_DOMINIO 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
Se o emissor for o seu próprio domínio quando você esperava uma autoridade, é o certificado padrão que está sendo servido: a obtenção falhou. Pela ordem: o DNS (dig +short SEU_DOMINIO), a abertura das portas e depois docker compose logs traefik | grep -iE 'acme|certificate|error'.
Operação cotidiana
docker compose ps # estado dos serviços
docker compose logs -f product-server # logs da aplicação
./install.sh --start # atualização de versão, após a etapa 1
docker compose down # parada, dados conservados
A base MongoDB e os arquivos do Garage vivem em diretórios do host (FOXPLAN_MONGO_DATA, FOXPLAN_GARAGE_DATA, veja Volumes e discos): eles sobrevivem às paradas, às atualizações de versão e a docker compose down -v. Uma atualização de versão também é feita em duas etapas: baixe as imagens da nova versão (etapa 1), altere FOXPLAN_VERSION no .env e depois execute novamente ./install.sh --start.
Um diretório de dados não é um backup: ele não protege nem de uma falha do disco, nem de uma exclusão por engano. Veja Backup e restauração.
Backup e restauração
O que salvar
| Elemento | Salvar | Por quê |
|---|---|---|
| MongoDB | Sim | Todos os dados de negócio: projetos, tarefas, orçamentos, usuários |
Arquivos .env* | Sim | Contêm chaves sem as quais os dados restaurados ficam inutilizáveis |
| Redis | Não | Cache e sessões: reconstrói-se sozinho, ao custo de uma nova conexão dos usuários |
| Arquivos | Sim, ao mesmo tempo que a base | Documentos, anexos, fotos de perfil: no armazenamento S3 (FILE_S3_*), não no MongoDB. Com o Garage, veja Arquivos armazenados no Garage |
.env_application junto com a basePor padrão, o SECRET_KEY_JWT também serve como chave de criptografia dos segredos SSO armazenados na base. Restaurar um dump do MongoDB sem essa chave torna as configurações SSO indecifráveis: elas estão lá, ilegíveis, e precisam ser inteiramente reinseridas. Um backup da base sem os segredos não é um backup completo.
Backup
O comando é executado no contêiner do MongoDB, que inclui as ferramentas necessárias:
docker compose exec -T mongo sh -c \
'mongodump --username "$MONGO_INITDB_ROOT_USERNAME" \
--password "$MONGO_INITDB_ROOT_PASSWORD" \
--authenticationDatabase admin --archive --gzip' \
> foxplan-$(date +%F-%H%M).archive.gz
Três detalhes que importam:
- As credenciais são lidas a partir do ambiente do contêiner, nunca escritas na linha de comando — portanto não aparecem nem no
psnem no histórico do shell. Uma senha com espaços ou caracteres especiais passa assim sem necessidade de escape. --archiveproduz um único arquivo em vez de uma árvore de diretórios: mais simples de transferir, criptografar e verificar.-Tdesativa a alocação de um pseudoterminal; caso contrário, o fluxo binário seria corrompido no redirecionamento.
O backup é feito a quente, sem parar a aplicação.
Restauração
docker compose exec -T mongo sh -c \
'mongorestore --username "$MONGO_INITDB_ROOT_USERNAME" \
--password "$MONGO_INITDB_ROOT_PASSWORD" \
--authenticationDatabase admin --archive --gzip --drop' \
< foxplan-2026-09-15-0300.archive.gz
--drop substitui cada coleção pela versão salva. Sem essa opção, a restauração seria mesclada com os dados presentes, o que resulta em um estado incoerente — raramente é o que se quer.
O Redis guarda objetos que referenciam os identificadores da base antiga. Após uma restauração, essas referências já não correspondem a nada e a aplicação devolve erros 500 desconcertantes — tipicamente um objeto «não encontrado» embora esteja presente na base.
docker compose exec redis sh -c 'redis-cli -a "$REDIS_PASSWORD" FLUSHALL'
docker compose restart product-server
É o esquecimento mais frequente após uma restauração.
Arquivos armazenados no Garage
O MongoDB referencia os arquivos pela sua chave: salve a base e os arquivos no mesmo momento, para poder restaurá-los juntos.
A quente, arquivo a arquivo — o método a privilegiar. O rclone copia o conteúdo do bucket para um diretório, sem parar o Garage; as execuções seguintes só transferem o que mudou. Com as credenciais do .env_application:
NET=$(docker network ls --format '{{.Name}}' | grep foxplan-network)
KEY=$(grep ^FILE_S3_ACCESS_KEY= .env_application | cut -d= -f2)
SECRET=$(grep ^FILE_S3_SECRET_KEY= .env_application | cut -d= -f2)
mkdir -p /var/backups/foxplan/files
docker run --rm --network "$NET" -v /var/backups/foxplan/files:/backup \
rclone/rclone:1.68.2 sync :s3:foxplan /backup \
--s3-provider Other --s3-endpoint http://garage:3900 --s3-region garage \
--s3-access-key-id "$KEY" --s3-secret-access-key "$SECRET"
Para restaurar, inverta a origem e o destino (sync /backup :s3:foxplan), em um Garage preparado como na instalação.
A frio, diretório completo — para uma migração de servidor. Pare o Garage, arquive o seu diretório (FOXPLAN_GARAGE_DATA, com as suas subpastas meta e data) e depois reinicie-o:
docker compose stop garage
sudo tar czf garage-$(date +%F).tar.gz -C /srv/foxplan/garage meta data
docker compose start garage
As duas subpastas andam juntas: dados sem os seus metadados são inutilizáveis. Tal como para o MongoDB, transfira os backups para fora do servidor.
Automatizar
Uma tarefa cron diária, com rotação de 30 dias:
# /etc/cron.d/foxplan-backup
0 3 * * * root cd /opt/foxplan && \
docker compose exec -T mongo sh -c 'mongodump --username "$MONGO_INITDB_ROOT_USERNAME" --password "$MONGO_INITDB_ROOT_PASSWORD" --authenticationDatabase admin --archive --gzip' \
> /var/backups/foxplan/foxplan-$(date +\%F).archive.gz 2>/var/log/foxplan-backup.log && \
find /var/backups/foxplan -name 'foxplan-*.archive.gz' -mtime +30 -delete
O % deve ser escapado como \% em uma tabela cron; caso contrário, a linha é truncada nesse ponto.
Um arquivo guardado no próprio servidor que ele salvaguarda desaparece com ele. Transfira-o para um armazenamento separado — armazenamento de objetos, servidor de backup, fita. É metade do trabalho, e é a metade que se esquece.
Verificar se o backup é utilizável
Um backup nunca restaurado não é um backup: é uma hipótese. Teste-o periodicamente em um ambiente separado — nunca na produção.
# Verificação rápida da integridade do arquivo, sem restaurar nada
gzip -t foxplan-2026-09-15-0300.archive.gz && echo "arquivo íntegro"
Depois, uma ou duas vezes por ano, faça uma restauração completa em um servidor de teste e conecte-se à aplicação: é o único controle que prova alguma coisa.
Alternativa: backup a frio da base
Para uma cópia bit a bit em vez de uma exportação lógica, pare a base e arquive o seu diretório (FOXPLAN_MONGO_DATA):
docker compose stop product-server mongo
sudo tar czf mongo-$(date +%F).tar.gz -C /srv/foxplan/mongo .
docker compose start mongo product-server
Mais rápido em uma base grande, mas com duas contrapartidas: a aplicação fica interrompida durante a operação, e o arquivo só pode ser restaurado em uma versão idêntica do MongoDB. O mongodump continua sendo o método a privilegiar; reserve este para as migrações de servidor.
Modo de manutenção
Uma página de manutenção ativável, que mantém /docs acessível durante a intervenção, é fornecida com o pacote de implantação acompanhada. Ela não está incluída nos arquivos para baixar acima. Entre em contato com o suporte para obtê-la.
Solução de problemas
| Sintoma | Pista |
|---|---|
manifest unknown na inicialização | FOXPLAN_VERSION inexistente |
pull access denied | FOXPLAN_REGISTRY ou FOXPLAN_IMAGE incorreto — a imagem se chama foxplan_prod — ou um docker login antigo recusado pelo registro: docker logout <host do registro> |
required variable FOXPLAN_REGISTRY is missing | Endereço do registro ausente do .env: informe o valor comunicado pelo FoxPlan |
env file .env_mongo not found | Os arquivos de segredos não foram criados — veja Segredos da aplicação |
404 em / | DOMAIN incorreto, ou aplicação não iniciada — veja os logs |
| 502 / 503 | A aplicação ainda está iniciando, ou parou por causa de um erro |
| Certificado inválido | DNS, portas, logs ACME — veja Verificar o certificado servido |
| Dúvida sobre uma integração | Conta FoxPlan → Diagnóstico: base, Redis, e-mails, S3 e exportação testados com um clique |
| Nenhum e-mail recebido | MAIL_PROXY_TYPE ou MAIL_SMTP_* ausentes — veja Envio de e-mails |
| E-mails recebidos, mas links inválidos | BASE_URL ausente ou diferente do domínio público |
| E-mails no spam | MAIL_FROM não autorizado a enviar pelo domínio (SPF/DKIM) |
| Erro de autenticação Redis | SPRING_REDIS_PASSWORD diferente de REDIS_PASSWORD |
| Falha de autenticação Mongo | URL_MONGO incoerente com .env_mongo |
| Exportações Gantt com falha | Serviço gantt-export parado, ou MSP_SERVICE_ENDPOINT inacessível |
| Erros 500 «objeto não encontrado» após uma restauração | Cache Redis não limpo — veja Restauração |
| Configuração SSO ilegível após uma restauração | SECRET_KEY_JWT diferente do que estava em vigor no momento do dump |
Armazenamento em erro no diagnóstico, 400 nos pedidos S3 | FILE_S3_REGION diferente da região do armazenamento (s3_region para o Garage) — veja Armazenamento de arquivos |
| Armazenamento em erro logo após uma troca de chave do Garage | A chave do .env_application não é a do Garage: docker compose exec garage /garage key info foxplan --show-secret |
| «Ficheiro demasiado grande» | Um arquivo ultrapassa 10 MB |
| «Quota de armazenamento atingida» | Apenas SaaS: a instância esgotou o seu espaço; uma licença pode ampliá-lo |
no matching manifest for linux/arm64 | As imagens de exportação são publicadas apenas para amd64: mantenha platform: linux/amd64 em gantt-export e gantt-msp (o Docker Desktop as emula) |
A aplicação reinicia em loop, Client id of registration 'google' must not be empty nos logs | Imagem anterior a outubro de 2026 e linhas de SSO Microsoft ou Google declaradas mas vazias em .env_application: atualize a imagem ou comente essas linhas |
| O FoxPlan reinicia sem dados após uma reinicialização do servidor | Disco de dados não montado: o MongoDB iniciou na pasta vazia do disco do sistema. findmnt /srv/foxplan, e depois a etapa 4 de Volumes e discos |
required variable FOXPLAN_MONGO_DATA is missing a value | Variável ausente do .env: veja Volumes e discos |
MongoDB vazio depois de definir FOXPLAN_MONGO_DATA | Dados que ficaram no volume Docker antigo: veja Instalação existente |
A aplicação reinicia em loop, https://${FILE_S3_URL} nos logs | Variáveis FILE_S3_* ausentes de .env_application: declare-as, mesmo vazias — os anexos ficarão indisponíveis |
PKIX path building failed nos logs da aplicação | Autoridade interna do AD FS, do SMTP ou do S3 ausente de ca-sortant/: veja Certificados de entrada e de saída |