SSD Nodes Learn 🎉 VPS desde $5.50/mês
Guias Matt ConnorPor Matt Connor · Atualizado 2026-08-10

Como hospedar o Planka com Docker Compose

Veja como hospedar o Planka em um VPS com Docker Compose, Postgres e Traefik, incluindo as variáveis de admin e o BASE_URL que causa falhas de login.

O que você obtém ao alojar o Planka por conta própria

A instalação autónoma do Planka 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 cenário considerado é uma equipa de duas a cinco pessoas que está a sair do escalão gratuito do Trello. Se ainda está a decidir que quadro utilizar, leia primeiro a comparação de alternativas autoalojadas ao Trello. Este guia pressupõe que a escolha já foi feita e aborda apenas a implementação.

É necessário um VPS com Docker Engine e o plugin Compose, além de um registo DNS A apontado para esse servidor. Também é necessária uma instância do Traefik já a terminar o TLS (segurança da camada de transporte) 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 é um padrão confortável do provedor, não um requisito medido pelo projeto. É um valor generoso para um quadro utilizado por cinco pessoas.

O que realmente é executado é pequeno: um processo Node.js que fornece a API e o frontend compilado, e um processo Postgres que armazena os dados. Um terceiro processo proxy pequeno é executado dentro do contêiner Planka para filtrar os pedidos de saída. Um plano com 1 vCPU e 2 GB suporta um quadro utilizado por duas a cinco pessoas. A maior parte da memória disponível acaba como cache do Postgres.

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 -v

O primeiro comando mostra a memória e a CPU utilizadas em tempo real por contêiner. O segundo mostra quanto espaço cada volume ocupa. Faça ambas as 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 equipa.

Escreva o ficheiro Compose

Crie o diretório e atribua-lhe a propriedade, para nunca ter de editar estes ficheiros através de sudo.

sudo mkdir -p /opt/planka
sudo chown "$USER":"$USER" /opt/planka
cd /opt/planka

Gere os segredos num ficheiro .env junto ao ficheiro Compose. O Compose lê esse ficheiro 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 .env

openssl rand -hex é intencional. Uma cadeia hexadecimal contém apenas dígitos e as letras de a a f, por isso não pode quebrar a cadeia de ligação DATABASE_URL onde é inserida. Uma palavra-passe base64 com uma barra ou um arroba provoca um erro de ligação que parece indicar um nome de anfitrião incorreto, fazendo-o perder uma hora. O padrão mais abrangente é explicado em manter os segredos fora do ficheiro Compose.

Agora docker-compose.yml. Substitua kanban.example.com pelo seu próprio nome de anfitrião nos dois locais onde 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 a pena explicar quatro decisões nesse ficheiro, porque são as que as pessoas alteram e depois lamentam.

  • Não existe nenhum bloco ports: no serviço Planka. O Traefik chega ao contentor através da rede proxy, por isso a porta 1337 nunca é publicada no anfitrião. Publicá-la permitiria a qualquer pessoa contornar o proxy e o certificado.
  • loadbalancer.server.port=1337 indica a porta dentro do contentor. O Planka escuta na porta 1337, e o exemplo original só chega a ela através da porta 3000 porque mapeia a porta para o anfitrião. Aqui não existe mapeamento para o anfitrião, por isso é necessário indicar ao Traefik a porta do contentor.
  • condition: service_healthy funciona em conjunto com o healthcheck do Postgres. Sem ele, o Planka arranca antes de a base de dados aceitar ligações, falha a primeira consulta e termina, o que parece um ciclo de falhas. Os detalhes estão em healthchecks do Compose e ordenação do arranque.
  • O serviço da base de dados chama-se postgres de propósito. O Planka 2 encaminha os seus próprios pedidos de saída através de um filtro interno cuja lista de bloqueio predefinida é localhost,postgres. Se mudar o nome do serviço, remove silenciosamente a sua base de dados dessa lista.

Confirme que o Compose consegue ver os seus segredos antes de iniciar qualquer componente:

docker compose config | grep -E 'image:|BASE_URL|POSTGRES_USER'

Esse comando mostra o ficheiro com os valores .env já substituídos. Um valor vazio significa que o Compose não está a ler o ficheiro .env, normalmente porque está a executar o comando a partir de um diretório diferente.

O que as variáveis de bootstrap do administrador realmente fazem

Desde o Planka 1.13, nenhum administrador é criado automaticamente. Por isso, uma base de dados nova não tem ninguém que possa iniciar sessão. O grupo DEFAULT_ADMIN_* é uma das duas formas de resolver esse problema.

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 servem para a gerir.

A variável DEFAULT_ADMIN_EMAIL tem uma segunda função que costuma causar confusão. Enquanto estiver definida, a conta que identifica não pode ser editada nem eliminada pela interface, por ninguém. Isto funciona como uma proteção contra bloqueio. Também explica por que não pode mudar o nome dessa conta nem o respetivo 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. Tudo o que estiver em environment: pode ser lido por qualquer pessoa que consiga 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 segura ignora completamente 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-user

O comando pede o endereço de 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. Use esta opção se mais de uma pessoa tiver acesso à shell da VPS. O comando inicia primeiro o Postgres por causa de depends_on. Por isso, funciona numa stack que nunca foi iniciada.

Qualquer uma das opções deixa a gestão das palavras-passe do Planka a seu cargo. Se essa for a quarta série de credenciais que a sua equipa acumulou, o Planka pode delegar os inícios de sessão num fornecedor OIDC, como o Authentik executado como o seu próprio servidor de início de sessão único, mantendo o administrador de bootstrap como uma 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 seus próprios links e a ligação WebSocket a partir desse valor. Por isso, um BASE_URL incorreto não apresenta um erro claro. Apresenta uma página que carrega e depois nunca termina de carregar.

O caso comum é este: copia o exemplo do upstream, deixa BASE_URL=http://localhost:3000 no lugar e acede ao site por HTTPS no seu 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 para /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 cada pedido chega-lhe a partir do endereço do proxy, através de HTTP simples dentro da rede Docker. Sem TRUST_PROXY, a aplicação ignora os cabeçalhos X-Forwarded-Proto e X-Forwarded-For que o Traefik define. Assim, considera que a ligação não é segura e trata todos os clientes como se tivessem o mesmo endereço IP. Com essa opção definida, a aplicação lê esses cabeçalhos e fica de acordo com o browser quanto ao esquema.

O Traefik faz proxy de WebSockets sem configuração adicional. Essa é uma das razões para o preferir neste caso. No nginx, o socket.io precisa do seu próprio bloco location, que deve incluir proxy_set_header Upgrade $http_upgrade e proxy_set_header Connection "upgrade". Caso contrário, verá o mesmo indicador de carregamento bloqueado, mas por outra causa.

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 de carregamento 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 março de 2026. Em tags mais antigas, atribua-lhe um subdomínio próprio.

Onde o Planka guarda anexos e avatares

O Planka 2 guarda tudo o que um utilizador carrega 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 mais antigo monta caminhos que já não existem, e o diretório de dados real fica sem montagem.

Essa montagem única determina se um quadro sobrevive a uma atualização ou se causa uma tarde de problemas. Se /app/data não estiver num volume, os ficheiros carregados ficam na camada gravável do contentor. Essa camada é eliminada 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 deixam de funcionar, 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. Uma bind mount também funciona e facilita a cópia de segurança dos ficheiros com ferramentas normais, mas exige um passo adicional. O processo Node dentro do contentor é executado com o UID 1000. Por isso, um diretório no anfitrião pertencente a root gera um erro de permissões no primeiro carregamento:

sudo chown -R 1000:1000 /opt/planka/data

A diferença entre as duas opções é explicada em bind mounts e volumes nomeados.

Se os anexos ultrapassarem o espaço em disco incluído no seu plano, o Planka também pode gravá-los num armazenamento compatível com S3, através de S3_ENDPOINT, S3_BUCKET e das variáveis de chave correspondentes. Isto pode apontar para um bucket alojado ou para um armazenamento de objetos MinIO autoalojado noutro servidor. Tome esta decisão antes de a equipa encher o quadro, porque a definição aplica-se aos novos carregamentos.

Inicie a stack e verifique se funcionou

docker compose pull
docker compose up -d
docker compose ps

docker compose ps deve mostrar postgres como healthy e planka como running. Se o Planka estiver a reiniciar continuamente, a ligação à base de dados é o primeiro ponto a verificar, não a aplicação.

docker compose logs -f planka

Um primeiro arranque saudável executa as migrações da base de dados e, em seguida, indica que o servidor está a escutar na porta 1337. Confirme diretamente no Postgres se o esquema foi realmente criado, em vez de confiar 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. A mensagem "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.

Em seguida, verifique a rota a partir da sua própria máquina, não a partir do VPS:

curl -I https://kanban.example.com

HTTP/2 200 significa que o Traefik tem um certificado e consegue alcançar o 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. Abra agora 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 board, 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 estiver em execução.

docker compose exec -T postgres pg_dump -U planka -d planka > "planka-db-$(date +%F).sql"

O -T é obrigatório. 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 durante o restauro. A falha só aparece semanas mais tarde, no pior momento possível.

Depois, trate dos uploads. Primeiro, descubra o nome real do volume, porque o Compose acrescenta o nome do diretório do projeto como prefixo.

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 numa tarefa cron noturna. Qualquer uma das abordagens é adequada. O que não é adequado é ter um backup que nunca foi restaurado. Por isso, restaure-o uma vez numa VPS temporária e confirme que consegue iniciar sessão e abrir um anexo.

Execute o dump imediatamente antes de cada alteração de versão. Um backup da noite anterior não é o mesmo que um backup feito antes da migração que está prestes a executar.

Fixe as tags e leia as notas da release

As duas tags de imagem nesse ficheiro estão fixadas de propósito.

ghcr.io/plankanban/planka:2.1.1 é uma release específica, atual em agosto de 2026. latest muda sempre que o upstream publica uma nova versão, por isso um docker compose pull de rotina pode aplicar uma migração de esquema num momento que não escolheu. Leia as notas da release 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 release de segurança. É exatamente o tipo de alteração que deve ler, em vez de a aplicar por acidente.

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 será iniciado:

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. A atualização para uma nova versão principal do Postgres exige um dump da versão antiga e um restore para um diretório de dados novo na versão nova. É uma operação planeada, 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 do zero, essa atualização tem um procedimento próprio documentado na documentação do projeto, e não há forma de voltar à versão 1 sem uma cópia de segurança criada previamente.

Modos de falha e as mensagens que verá

O Planka reinicia em loop 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 é bem-sucedido, 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 uploads falham, mas todo o resto funciona. Um bind mount é propriedade de 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 alterar novamente 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 os 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 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 por conceção. Ajuste OUTGOING_ALLOWED_HOSTS em vez de remover o filtro.

Depois de estar em execução, a carga operacional é reduzida. Consulte as notas de lançamento e faça um dump da base de dados antes de cada atualização. Um reboot coloca a stack novamente em execução por si só devido a restart: unless-stopped, desde que o serviço Docker esteja configurado para arrancar no boot. 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 de eu iniciar sessão?

As credenciais foram aceitas, mas a ligação em tempo real não foi estabelecida. O Planka cria o URL do WebSocket a partir de BASE_URL. Se essa variável ainda indicar http://localhost:3000, enquanto acede ao site em https://kanban.example.com, o navegador tenta abrir um socket para um endereço que não existe na sua máquina. A consola do programador mostra pedidos falhados para /socket.io/. Defina BASE_URL com o endereço público exato, sem barra final, adicione TRUST_PROXY=true para que a aplicação respeite o cabeçalho X-Forwarded-Proto do seu 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. Defina DEFAULT_ADMIN_EMAIL com as variáveis correspondentes de palavra-passe, nome e nome de utilizador e inicie a stack, ou execute docker compose run --rm planka npm run db:create-admin-user e responda às perguntas. O comando interativo é mais seguro num servidor partilhado, porque a palavra-passe nunca entra no ambiente do contentor, onde docker inspect pode lê-la. Manter DEFAULT_ADMIN_EMAIL definido depois bloqueia essa conta contra alterações e eliminação 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 o volume não estiver montado, os ficheiros ficam na camada gravável do contentor e são destruídos na próxima recriação do contentor, que ocorre em cada atualização da imagem. Também pode usar um bind mount, 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 instância Planka autoalojada?

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 é um padrão do fornecedor, não uma medição, e é generoso para um quadro pequeno. Um processo Node e um processo Postgres compõem toda a carga de trabalho. Por isso, um plano com 1 vCPU e 2 GB suporta uma equipa de dois a cinco utilizadores. 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?

Faça um dump da base de dados e arquive o volume de carregamentos imediatamente antes da atualização, não com base no agendamento da noite anterior. Use docker compose exec -T postgres pg_dump -U planka -d planka > planka-db.sql e mantenha -T para que o pseudo-terminal não corrompa a saída redirecionada. Leia as notas de versão de todas as versões ignoradas, 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 em busca da migração. Mantenha a tag do Postgres fixa na respetiva versão principal, porque o servidor recusa abrir um diretório de dados escrito por uma versão principal diferente.