Portainer e Nginx Proxy Manager

Este caminho substitui o proxy Caddy do guia Ubuntu por Portainer e Nginx Proxy Manager (NPM). O banco, Redis e MinIO permanecem privados na rede Docker; o NPM é o único componente que publica HTTP/HTTPS.

Visão geral

push em main → GitHub Actions → ghcr.io/seu-usuario/seu-fork:sha-<commit>
                                      │
                                      └→ SSH no manager → docker service update
VPS: Nginx Proxy Manager → Stack Madmail → PostgreSQL / Redis / MinIO

O workflow incluído em .github/workflows/deploy-fork.yml publica a imagem do seu fork no GitHub Container Registry (GHCR) e atualiza o serviço do Docker Swarm no manager via SSH. O Portainer CE continua sendo a interface de administração da Stack.

Atenção

Não use Watchtower para uma Stack Docker Swarm: ele tenta recriar containers diretamente e falha em redes overlay, que não são manualmente conectáveis. O usuário SSH de deploy deve ter permissão para executar docker service update no manager.

1. Criar uma rede compartilhada para o NPM

No host Docker, descubra a rede do NPM com docker network ls. Se ele já tiver uma rede externa compartilhada, use o nome dela no lugar de npm_proxy no arquivo da Stack. Caso contrário, crie a rede e conecte o contêiner NPM existente uma única vez:

docker network create npm_proxy
docker network connect npm_proxy <nome-ou-id-do-container-npm>

O arquivo docker/portainer/compose.yml declara essa rede como externa. No seu fork, use esse mesmo arquivo; não publique portas para usesend ou minio.

2. Criar a Stack no Portainer

Em Stacks → Add stack, escolha Repository, informe a URL do seu fork, a branch main e o caminho docker/portainer/compose.yml. Para uma Stack vinculada ao Git, toda mudança na composição deve ser feita no Git, não no editor do Portainer.

Em Environment variables, cadastre os valores abaixo. Guarde os valores secretos somente no Portainer, não em .env no Git.

VariávelValor/exemplo
USESEND_IMAGEghcr.io/SEU_USUARIO/SEU_FORK:latest
GHCR_USERNAME, GHCR_READ_TOKENusuário GitHub e PAT clássico limitado a read:packages
POSTGRES_USER, POSTGRES_DBusesend
POSTGRES_PASSWORDsegredo aleatório (openssl rand -hex 32)
DATABASE_URLpostgresql://usesend:SENHA@postgres:5432/usesend
REDIS_URL, REDIS_KEY_PREFIXredis://redis:6379, usesend
NEXTAUTH_URL, NEXTAUTH_SECREThttps://send.exemplo.com, segredo aleatório
NEXT_PUBLIC_IS_CLOUDfalse
AWS_DEFAULT_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEYcredenciais AWS SES/SNS
GITHUB_ID, GITHUB_SECRET ou GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET ou FROM_EMAILao menos um método de login
MINIO_ROOT_USER, MINIO_ROOT_PASSWORDusuário e segredo MinIO
S3_COMPATIBLE_ACCESS_KEY, S3_COMPATIBLE_SECRET_KEYos mesmos dados de acesso do MinIO neste exemplo
S3_COMPATIBLE_API_URL, S3_COMPATIBLE_PUBLIC_URLhttps://storage.exemplo.com
S3_COMPATIBLE_BUCKETunsend
API_RATE_LIMIT, AUTH_EMAIL_RATE_LIMIT1, 5

Se o pacote GHCR do fork for privado, crie em Registries uma credencial para ghcr.io com seu usuário GitHub e um Personal Access Token clássico limitado a read:packages; associe esse registry à Stack. Como alternativa, torne somente o pacote de imagem público.

Faça o primeiro deploy. A imagem executa migrations pendentes antes de iniciar; verifique os logs da Stack e só então faça login. O primeiro usuário é o bootstrap da instalação self-hosted.

3. Criar os Proxy Hosts no Nginx Proxy Manager

Crie dois Proxy Hosts. Ambos usam a rede Docker compartilhada, por isso o NPM encaminha pelo nome do serviço, sem expor portas no VPS.

DomínioSchemeForward HostnamePortaOpções
send.exemplo.comhttpusesend_usesend3000Websockets Support, Block Common Exploits, certificado Let's Encrypt e Force SSL
storage.exemplo.comhttpusesend_minio9000certificado Let's Encrypt e Force SSL

Os dois registros DNS devem apontar para o IP do VPS. storage.exemplo.com precisa ser público: o navegador recebe URLs assinadas para enviar imagens e anexos. O console administrativo do MinIO não é publicado por esta Stack.

Nota

Em Docker Swarm, use o nome completo do serviço, no formato <nome-da-stack>_<nome-do-serviço>, pois o NPM é uma Stack ou container separado. Para uma Stack chamada usesend, os destinos são usesend_usesend:3000 e usesend_minio:9000. O NPM também precisa participar da mesma rede overlay externa npm_public. Se ele for um container Docker comum, essa rede deve ter sido criada com --attachable; o caminho mais simples é executar o próprio NPM como serviço Swarm nessa rede.

Atualize a URL de callback do GitHub OAuth para https://send.exemplo.com/api/auth/callback/github. No painel do Madmail, configure a região SES, a URL pública da aplicação para callbacks SNS e o domínio de envio.

4. Ativar o deploy do fork pelo GitHub Actions

O workflow já foi adicionado em .github/workflows/deploy-fork.yml. A cada push em main, ele:

  1. constrói docker/Dockerfile;
  2. publica ghcr.io/<dono-do-fork>/<nome-do-fork>:latest e uma tag imutável com SHA;
  3. conecta por SSH ao manager do Swarm e atualiza o serviço com a tag SHA imutável.

No GitHub do fork, abra Settings → Secrets and variables → Actions e crie:

  • DEPLOY_HOST: IP ou hostname do nó manager do Swarm;
  • DEPLOY_USER: usuário SSH com acesso ao Docker;
  • DEPLOY_SSH_PRIVATE_KEY: chave privada Ed25519 de deploy;
  • DEPLOY_KNOWN_HOSTS: resultado de ssh-keyscan -H SEU_HOST obtido e verificado previamente;
  • GHCR_USERNAME e GHCR_READ_TOKEN: usuário GitHub e PAT clássico read:packages;
  • SWARM_SERVICE_NAME: usesend_usesend se a Stack se chamar usesend.

Nunca coloque a chave privada ou o PAT no repositório. Sem todos esses segredos, a Action somente publica a imagem e termina com sucesso, sem atualizar o VPS.

O workflow usa GITHUB_TOKEN com a permissão packages: write; não é necessário salvar credencial de publicação. O token read:packages é enviado ao manager somente para o docker service update --with-registry-auth distribuir a imagem privada aos nós. Depois do primeiro push, confirme em Services no Portainer que usesend_usesend criou uma nova task.

Dica

Para validar antes de produção, mude o gatilho do workflow para uma branch staging e aponte uma segunda Stack para ghcr.io/...:latest daquela branch, ou use a tag SHA publicada pelo job para fazer rollback manual.

Atualização, rollback e backup

Para uma atualização manual no Portainer, abra a Stack e use Pull and redeploy. Para rollback, altere USESEND_IMAGE para a tag SHA de uma execução conhecida, por exemplo ghcr.io/seu-usuario/seu-fork:sha-<commit>, e redeploy.

Faça backup dos volumes usesend_postgres-data e usesend_minio-data (o prefixo pode variar conforme o nome da Stack). Um dump lógico do banco continua sendo a opção mais portátil:

docker exec -t usesend-postgres-1 pg_dump -U usesend usesend | gzip > usesend-$(date +%F).sql.gz

Confirme o nome real do contêiner em Containers antes de executar o comando. Nunca use Remove stack com a opção de apagar volumes em uma instalação com dados.