Como hospedar o Planka com Docker Compose
Implante o Planka em um VPS com Docker Compose, Postgres e Traefik. Veja as variáveis de bootstrap do admin e como BASE_URL causa falhas de login.
O que você obtém ao alojar o Planka por conta própria
A utilização do Planka no seu próprio servidor fornece à sua equipa um quadro Kanban com o modelo de cartões, listas e etiquetas que as pessoas já conhecem do Trello, executado num VPS sob o seu controlo. Não existem limites de utilizadores nem cobrança por utilizador, porque o único custo é o servidor. Este guia implementa o serviço com Docker Compose atrás do Traefik, usando Postgres para os dados e um volume nomeado para cada ficheiro carregado pelos utilizadores.
O público-alvo é uma equipa de duas a cinco pessoas que está a deixar o nível gratuito do Trello. Se ainda estiver a decidir qual quadro utilizar, leia primeiro a comparação de alternativas autoalojadas ao Trello. Este guia parte do princípio de que a escolha já foi feita e abrange apenas a implementação.
É necessário um VPS com Docker Engine e o plugin Compose, além de um registo DNS A que aponte para esse VPS. Também é necessária uma instância do Traefik já a terminar o TLS (transport layer security) nesse servidor. Se o Traefik ainda não estiver configurado, configure primeiro um reverse proxy Traefik à frente de várias aplicações Compose e leia os conceitos básicos do Docker Compose para um VPS se o ficheiro abaixo não lhe for familiar.
Quanto de VPS o Planka precisa?
O projeto não publica um requisito mínimo de hardware, portanto trate qualquer número que encontrar como um ponto de partida, não como uma medição. O valor de 2 vCPU e 4 GB repetido nas páginas de hospedagem é uma configuração padrão confortável do provedor, não um requisito medido pelo projeto. É generoso para um quadro utilizado por cinco pessoas.
O que realmente é executado é pequeno: um processo Node.js que disponibiliza a API e o frontend compilado, e um processo Postgres que armazena os dados. Um terceiro processo de proxy, também pequeno, é executado dentro do contêiner do Planka para filtrar as solicitações de saída. Um plano com 1 vCPU e 2 GB suporta um quadro utilizado por duas a cinco pessoas, e a maior parte da memória livre acaba sendo usada como cache do Postgres. Um quadro consome poucos recursos, portanto, se o mesmo VPS também for usado para armazenar os documentos da sua equipe, dimensione primeiro para essa aplicação: executar o AFFiNE como um espaço de trabalho no estilo do Notion precisa de alguns gigabytes próprios antes que o Planka solicite qualquer recurso.
Dimensione o disco antes de dimensionar a memória, porque os anexos são o componente que cresce. Meça a sua própria instância em vez de confiar neste parágrafo:
docker stats --no-stream
docker system df -vO primeiro comando mostra a memória e a CPU utilizadas por cada contêiner em tempo real. O segundo mostra quanto espaço cada volume ocupa. Faça as duas medições depois de uma semana normal de trabalho, não no dia da instalação, porque um quadro inativo não revela nada sobre a sua equipe.
Escreva o arquivo Compose
Crie o diretório e atribua-lhe a propriedade, para nunca ter de editar estes arquivos através de sudo.
sudo mkdir -p /opt/planka
sudo chown "$USER":"$USER" /opt/planka
cd /opt/plankaGere os secrets em um arquivo .env ao lado do arquivo Compose. O Compose lê esse arquivo automaticamente e substitui os valores.
umask 077
{
printf 'SECRET_KEY=%s\n' "$(openssl rand -hex 64)"
printf 'POSTGRES_PASSWORD=%s\n' "$(openssl rand -hex 24)"
printf 'ADMIN_PASSWORD=%s\n' "$(openssl rand -hex 12)"
} > .env
chmod 600 .envopenssl rand -hex é intencional. Uma string hexadecimal contém apenas dígitos e as letras de a a f, portanto não pode quebrar a string de conexão DATABASE_URL na qual é inserida. Uma senha base64 com uma barra ou um sinal de arroba gera um erro de conexão que parece indicar um hostname incorreto, fazendo você perder uma hora. O padrão mais amplo é explicado em manter secrets fora do arquivo Compose.
Agora docker-compose.yml. Substitua kanban.example.com pelo seu próprio hostname nos dois locais em que aparece.
services:
planka:
image: ghcr.io/plankanban/planka:2.1.1
restart: unless-stopped
volumes:
- planka-data:/app/data
environment:
- BASE_URL=https://kanban.example.com
- DATABASE_URL=postgresql://planka:${POSTGRES_PASSWORD}@postgres/planka
- SECRET_KEY=${SECRET_KEY}
- TRUST_PROXY=true
- DEFAULT_ADMIN_EMAIL=you@example.com
- DEFAULT_ADMIN_PASSWORD=${ADMIN_PASSWORD}
- DEFAULT_ADMIN_NAME=Your Name
- DEFAULT_ADMIN_USERNAME=admin
networks:
- proxy
- internal
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.routers.planka.rule=Host(`kanban.example.com`)"
- "traefik.http.routers.planka.entrypoints=websecure"
- "traefik.http.routers.planka.tls.certresolver=default"
- "traefik.http.services.planka.loadbalancer.server.port=1337"
depends_on:
postgres:
condition: service_healthy
postgres:
image: postgres:16-alpine
restart: unless-stopped
volumes:
- db-data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=planka
- POSTGRES_USER=planka
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
networks:
- internal
healthcheck:
test: ["CMD-SHELL", "pg_isready -U planka -d planka"]
interval: 10s
timeout: 5s
retries: 5
volumes:
planka-data:
db-data:
networks:
proxy:
external: true
internal:Vale explicar quatro decisões nesse arquivo, pois são as que as pessoas alteram e depois lamentam.
- Não há um bloco
ports:no serviço Planka. O Traefik alcança o container pela redeproxy, portanto a porta 1337 nunca é publicada no host. Publicá-la permitiria que qualquer pessoa contornasse o proxy e o certificado. loadbalancer.server.port=1337define a porta dentro do container. O Planka escuta na porta 1337, e o exemplo upstream só o alcança na porta 3000 porque mapeia a porta para o host. Como não há mapeamento para o host aqui, o Traefik precisa receber a porta do container.condition: service_healthyé usado junto com o healthcheck do Postgres. Sem ele, o Planka inicia antes de o banco de dados aceitar conexões, falha na primeira consulta e termina, o que parece um loop de falhas. Os detalhes estão em healthchecks e ordenação de inicialização no Compose.- O serviço de banco de dados recebe o nome
postgresde propósito. O Planka 2 encaminha as próprias solicitações de saída por um filtro interno cuja lista de bloqueio padrão élocalhost,postgres. Se você renomear o serviço, removerá silenciosamente o banco de dados dessa lista.
Verifique se o Compose consegue acessar os secrets antes de iniciar qualquer coisa:
docker compose config | grep -E 'image:|BASE_URL|POSTGRES_USER'Esse comando mostra o arquivo com os valores de .env já substituídos. Um valor vazio significa que o Compose não está lendo o arquivo .env, geralmente porque o comando está sendo executado em outro diretório.
O que as variáveis de bootstrap do administrador fazem
Desde o Planka 1.13, nenhum administrador é criado automaticamente. Uma base de dados nova fica sem utilizadores que possam iniciar sessão. O grupo DEFAULT_ADMIN_* é uma das duas formas de resolver essa situação.
No arranque, o Planka procura um utilizador que corresponda a DEFAULT_ADMIN_EMAIL. Se não encontrar nenhum, cria um utilizando a palavra-passe, o nome de apresentação e o nome de utilizador definidos juntamente com essa variável. Isto acontece no primeiro arranque com uma base de dados vazia. Portanto, estas variáveis criam uma conta inicial, mas não a administram depois.
A variável DEFAULT_ADMIN_EMAIL tem uma segunda função que costuma causar confusão. Enquanto estiver definida, a conta indicada por ela não pode ser editada nem eliminada pela interface, por ninguém. Isto funciona como proteção contra bloqueio e também explica por que não pode alterar o nome dessa conta nem o endereço de e-mail na interface. Remova a variável e reinicie o serviço. A conta passa a ser uma conta de administrador normal, que pode editar como qualquer outra.
A linha da palavra-passe exige atenção. Qualquer valor em environment: pode ser lido por quem conseguir executar docker inspect no contentor. Por isso, DEFAULT_ADMIN_PASSWORD não deve permanecer aí. Inicie sessão, altere a palavra-passe na interface, elimine essa linha e execute docker compose up -d novamente.
A opção mais limpa não utiliza as variáveis. Comente todo o grupo DEFAULT_ADMIN_* e crie a conta de forma interativa:
docker compose run --rm planka npm run db:create-admin-userO comando pede o e-mail, a palavra-passe, o nome de apresentação e um nome de utilizador opcional. Depois, grava o utilizador diretamente na base de dados. A palavra-passe nunca passa pelo ficheiro Compose nem pelo ambiente do contentor. Utilize esta opção se mais de uma pessoa tiver acesso shell ao VPS. O comando inicia primeiro o Postgres por causa de depends_on, pelo que funciona numa stack que ainda nunca foi iniciada.
As duas opções deixam a gestão das palavras-passe do Planka a seu cargo. Se esta for a quarta credencial que a sua equipa tem de gerir, o Planka pode delegar os inícios de sessão num fornecedor OIDC, como o Authentik executado como o seu próprio servidor de single sign-on, mantendo o administrador inicial como conta de emergência para utilizar quando o fornecedor estiver indisponível.
Por que BASE_URL interrompe os logins quando não corresponde ao hostname
BASE_URL é o endereço exato que as pessoas introduzem no browser, com o esquema e sem uma barra final. Para esta stack, é https://kanban.example.com. O Planka cria os próprios links e a ligação WebSocket a partir desse valor. Por isso, um BASE_URL incorreto não produz um erro claro. Produz uma página que carrega e depois nunca termina de carregar.
O caso comum é este: copia o exemplo fornecido pelo projeto, deixa BASE_URL=http://localhost:3000 no lugar e acede ao site por HTTPS no domínio real. O formulário de login é enviado e as credenciais são aceites. O quadro nunca aparece. Abra a consola de desenvolvimento do browser. Verá pedidos a /socket.io/ a falhar, porque o cliente recebeu instruções para abrir a ligação em tempo real para localhost:3000. No seu portátil, esse endereço não corresponde a nada.
TRUST_PROXY=true é a outra metade do mesmo problema. O Planka está atrás do Traefik, por isso todos os pedidos chegam a partir do endereço do proxy e usam HTTP simples dentro da rede Docker. Sem TRUST_PROXY, a aplicação ignora os cabeçalhos X-Forwarded-Proto e X-Forwarded-For definidos pelo Traefik. Assim, considera a ligação insegura e trata todos os clientes como se tivessem o mesmo endereço IP. Com a opção definida, a aplicação lê esses cabeçalhos e usa o mesmo esquema que o browser.
O Traefik encaminha WebSockets sem configuração adicional. Esta é uma das razões para o preferir neste caso. No nginx, o socket.io precisa do seu próprio bloco location, com proxy_set_header Upgrade $http_upgrade e proxy_set_header Connection "upgrade". Caso contrário, verá o mesmo indicador de carregamento bloqueado, mas por uma causa diferente.
Mover o quadro para um novo hostname mais tarde implica alterar duas coisas em conjunto: o valor de BASE_URL e a regra Host() do Traefik. Se alterar uma e se esquecer da outra, o indicador volta a ficar bloqueado. Servir o Planka a partir de um subcaminho, como https://example.com/planka, funciona a partir da versão 2.1.0, lançada em March 2026. Nas tags mais antigas, atribua-lhe um subdomínio próprio.
Onde o Planka armazena anexos e avatares
O Planka 2 armazena tudo o que um utilizador envia num único caminho dentro do contentor: /app/data. Os anexos, os avatares dos utilizadores e as imagens de fundo dos quadros ficam todos nesse caminho. A versão 1 usava três diretórios separados. Por isso, um ficheiro Compose copiado de um guia antigo monta caminhos que já não existem, e o diretório de dados real fica sem montagem.
Essa montagem única é a diferença entre um quadro que sobrevive a uma atualização e uma tarde perdida. Se /app/data não estiver num volume, os ficheiros enviados ficam na camada gravável do contentor. Essa camada é destruída quando o contentor é recriado. O contentor é recriado sempre que altera a tag da imagem. O quadro volta a aparecer corretamente, todos os cartões continuam presentes e todas as ligações para anexos ficam inativas, porque as linhas da base de dados continuam a apontar para ficheiros que já não existem.
O volume nomeado no ficheiro Compose acima evita este problema. Também pode usar uma montagem bind. Esta torna os ficheiros mais fáceis de incluir em cópias de segurança com ferramentas comuns, mas requer um passo adicional. O processo Node dentro do contentor é executado com o UID 1000. Por isso, um diretório no host pertencente a root gera um erro de permissões no primeiro envio:
sudo chown -R 1000:1000 /opt/planka/dataA diferença entre as duas opções é explicada em montagens bind e volumes nomeados.
Se os anexos ocuparem mais espaço do que o disponível no seu plano, o Planka pode escrevê-los num armazenamento compatível com S3 através de S3_ENDPOINT, S3_BUCKET e das variáveis de chave correspondentes. Pode apontar para um bucket alojado ou para um armazenamento de objetos MinIO autoalojado noutro servidor. Tome esta decisão antes de a equipa preencher o quadro, porque a definição aplica-se aos novos envios.
Inicie a stack e confirme que funcionou
docker compose pull
docker compose up -d
docker compose psdocker compose ps deve mostrar postgres como healthy e planka como running. Se o Planka estiver a reiniciar em loop, a ligação à base de dados é o primeiro ponto a verificar, não a aplicação.
docker compose logs -f plankaUm primeiro arranque saudável executa as migrações da base de dados e depois indica que o servidor está a escutar na porta 1337. Confirme diretamente no Postgres se o esquema foi realmente criado, em vez de confiar apenas no log:
docker compose exec postgres psql -U planka -d planka -c '\dt'Uma lista de tabelas que inclua board e card significa que as migrações foram executadas. "Did not find any relations" significa que o Planka nunca se ligou à base de dados. Compare DATABASE_URL com os valores POSTGRES_USER e POSTGRES_PASSWORD no seu .env.
Depois, verifique a rota a partir do seu próprio computador, não a partir do VPS:
curl -I https://kanban.example.comHTTP/2 200 significa que o Traefik tem um certificado e consegue chegar ao contentor. Um erro 404 servido pelo Traefik significa que os labels do router não correspondem, normalmente porque o contentor não está ligado à rede proxy. Agora abra o site e inicie sessão com a conta de administrador.
Faça um pg_dump antes de cada atualização de versão
Dois armazenamentos separados contêm os dados do seu quadro. Por isso, o backup tem de abranger ambos: a base de dados Postgres e o volume planka-data. Faça o dump da base de dados enquanto a stack está em execução.
docker compose exec -T postgres pg_dump -U planka -d planka > "planka-db-$(date +%F).sql"O -T não é opcional. Sem ele, o Compose aloca um pseudo-terminal, e a camada do terminal reescreve as quebras de linha no fluxo. O resultado é um ficheiro de dump que falha a meio do restauro. A falha só aparece semanas depois, no pior momento possível.
Depois, trate dos uploads. Primeiro, encontre o nome real do volume, porque o Compose lhe acrescenta o nome do diretório do projeto.
docker volume ls | grep planka
docker run --rm -v planka_planka-data:/data -v "$PWD":/backup alpine \
tar czf /backup/planka-files-$(date +%F).tgz -C /data .O projeto também inclui docker-backup.sh e docker-restore.sh no respetivo repositório, e a documentação oficial recomenda executá-los através de um cron noturno. Qualquer uma das abordagens é adequada. O que não é adequado é ter um backup que nunca foi restaurado. Por isso, restaure um backup num VPS temporário e confirme que consegue iniciar sessão e abrir um anexo. O mesmo par de armazenamentos aparece em todas as aplicações Compose que aceitam uploads. Assim, se mais tarde instalar o Chatwoot na mesma máquina que o seu sistema de suporte, a rotina criada aqui poderá ser reutilizada, alterando pouco mais do que os nomes dos volumes.
Execute o dump imediatamente antes de cada alteração de versão. Um backup da noite anterior não é equivalente a um backup feito antes da migração que está prestes a executar.
Fixe as tags e leia as notas da versão
As duas tags de imagem nesse ficheiro estão fixadas de propósito.
ghcr.io/plankanban/planka:2.1.1 é uma versão específica, atual em agosto de 2026. latest muda sempre que o upstream publica uma versão, pelo que um docker compose pull de rotina pode aplicar uma migração de esquema num momento que não escolheu. Leia as notas da versão antes de alterar esse número, porque é aí que são descritas as alterações incompatíveis e as correções de segurança. A versão 2.0.3 foi publicada como uma versão de segurança. É precisamente o tipo de alteração que deve ler, em vez de a aplicar por acidente. Fixar a versão é simples neste caso porque o upstream publica imagens. Quando um projeto não publica imagens, deve aplicar a mesma disciplina com um passo adicional, como em openGym compilado no servidor a partir de uma tag git obtida por checkout.
postgres:16-alpine está fixada numa versão principal por uma razão mais importante. O Postgres grava o diretório de dados num formato associado à versão principal, e o servidor recusa abrir um diretório gravado por uma versão diferente. Escreva postgres:latest, deixe a tag avançar para 17 e o contentor não iniciará:
FATAL: database files are incompatible with server
DETAIL: The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17.Nada é perdido e reiniciar também não resolve o problema. Mudar para uma nova versão principal do Postgres exige criar um dump a partir da versão antiga e restaurá-lo num diretório de dados novo na versão nova. Esse é um trabalho planeado, com a stack parada, e não um efeito secundário de um pull de imagem.
Se estiver a migrar uma instalação Planka 1.x existente, em vez de começar de novo, essa atualização tem o seu próprio procedimento documentado na documentação do projeto, e não existe forma de voltar à versão 1 sem uma cópia de segurança criada previamente.
Modos de falha e mensagens que verá
O Planka reinicia continuamente e o log menciona a base de dados. As credenciais em DATABASE_URL não correspondem ao ambiente do Postgres. Tenha em atenção que POSTGRES_PASSWORD só é aplicado quando o diretório de dados é inicializado pela primeira vez. Por isso, corrigir a variável depois de um primeiro arranque incorreto não altera nada. Tem de remover o volume db-data e iniciar novamente.
O início de sessão funciona, mas o quadro nunca carrega. BASE_URL não corresponde ao endereço na barra do navegador, ou TRUST_PROXY está em falta. A consola do navegador mostra pedidos falhados para /socket.io/.
Os carregamentos falham, mas tudo o resto funciona. A montagem bind pertence a root. Execute sudo chown -R 1000:1000 no diretório do host e reinicie o contentor.
Os anexos desapareceram depois de uma atualização. /app/data não estava num volume. Por isso, os ficheiros estavam na camada do contentor que a atualização substituiu. Restaure os ficheiros a partir de uma cópia de segurança e adicione o volume antes de voltar a alterar a tag da imagem.
O Traefik devolve 404. O contentor não está na rede proxy, ou a regra Host() não corresponde ao seu registo DNS. docker compose config mostra as labels depois da substituição, que é onde os erros de escrita ficam visíveis.
As notificações ou os webhooks nunca chegam. O Planka 2 envia os seus pedidos HTTP de saída através de um filtro interno, e a lista de bloqueio predefinida inclui localhost e postgres. Um webhook direcionado para outro contentor no mesmo host pode ser bloqueado intencionalmente. Ajuste OUTGOING_ALLOWED_HOSTS em vez de remover o filtro.
Depois de estar em execução, a carga operacional é reduzida. Consulte as notas de versão e faça um dump da base de dados antes de cada atualização. Um reboot inicia novamente a stack por si só devido a restart: unless-stopped, desde que o serviço Docker esteja ativado no arranque, e Stacks Compose que voltam a arrancar depois de um reboot aborda os casos em que isso não acontece.
FAQ
Por que o Planka fica carregando indefinidamente depois do login?
As credenciais foram aceites, mas a ligação em tempo real não foi estabelecida. O Planka constrói o URL do WebSocket a partir de BASE_URL. Se essa variável ainda indicar http://localhost:3000 quando acede ao site em https://kanban.example.com, o navegador tenta abrir um socket para um endereço que não existe na máquina. A consola do programador mostra pedidos falhados para /socket.io/. Defina BASE_URL com o endereço público exato, sem barra no fim, adicione TRUST_PROXY=true para a aplicação respeitar o cabeçalho X-Forwarded-Proto do reverse proxy e execute docker compose up -d.
Como crio o primeiro utilizador administrador do Planka?
Desde a versão 1.13, nenhum administrador é criado automaticamente. Pode definir DEFAULT_ADMIN_EMAIL com as variáveis correspondentes de palavra-passe, nome e nome de utilizador e iniciar a stack, ou executar docker compose run --rm planka npm run db:create-admin-user e responder às perguntas. O comando interativo é mais seguro num servidor partilhado, porque a palavra-passe nunca entra no ambiente do contentor, onde docker inspect a pode ler. Manter DEFAULT_ADMIN_EMAIL definido depois bloqueia a edição e a eliminação dessa conta pela interface.
Onde é que o Planka armazena os anexos e os avatares?
Todos os ficheiros carregados ficam em /app/data dentro do contentor no Planka 2, incluindo anexos, avatares de utilizadores e fundos dos quadros. Monte esse caminho num volume nomeado. Se não estiver montado, os ficheiros ficam na camada gravável do contentor e são destruídos quando o contentor for recriado, o que acontece em cada atualização da imagem. Um bind mount também funciona, mas o processo Node é executado como UID 1000. Por isso, execute sudo chown -R 1000:1000 no diretório do host. Caso contrário, os carregamentos falham com um erro de permissões.
De quanta RAM precisa uma instalação do Planka alojada localmente?
O projeto não publica um requisito mínimo de hardware. O valor de 2 vCPU e 4 GB repetido nas páginas de alojamento é o padrão de um fornecedor, não uma medição, e é generoso para um quadro pequeno. Um processo Node e um processo Postgres constituem toda a carga. Por isso, um plano com 1 vCPU e 2 GB suporta uma equipa de duas a cinco pessoas. Execute docker stats --no-stream depois de uma semana normal e dimensione com base nos seus próprios valores. Monitorize o disco com mais atenção do que a memória, porque são os anexos que aumentam.
Como atualizo o Planka sem perder dados?
Despeje a base de dados e arquive o volume de uploads imediatamente antes da atualização, e não segundo o agendamento da noite anterior. Use docker compose exec -T postgres pg_dump -U planka -d planka > planka-db.sql, mantendo -T para que o pseudo-terminal não corrompa a saída redirecionada. Leia as notas de lançamento de todas as versões que ignorar, altere a tag da imagem para uma versão específica em vez de latest, execute docker compose pull e docker compose up -d e monitorize o log da migração. Mantenha a tag do Postgres fixada na versão major, porque o servidor recusa abrir um diretório de dados escrito por uma versão major diferente.