Madmail

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.

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ável Valor/exemplo
USESEND_IMAGE ghcr.io/SEU_USUARIO/SEU_FORK:latest
GHCR_USERNAME, GHCR_READ_TOKEN usuário GitHub e PAT clássico limitado a read:packages
POSTGRES_USER, POSTGRES_DB usesend
POSTGRES_PASSWORD segredo aleatório (openssl rand -hex 32)
DATABASE_URL postgresql://usesend:SENHA@postgres:5432/usesend
REDIS_URL, REDIS_KEY_PREFIX redis://redis:6379, usesend
NEXTAUTH_URL, NEXTAUTH_SECRET https://send.exemplo.com, segredo aleatório
NEXT_PUBLIC_IS_CLOUD false
AWS_DEFAULT_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY credenciais AWS SES/SNS
GITHUB_ID, GITHUB_SECRET ou GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET ou FROM_EMAIL ao menos um método de login
MINIO_ROOT_USER, MINIO_ROOT_PASSWORD usuário e segredo MinIO
S3_COMPATIBLE_ACCESS_KEY, S3_COMPATIBLE_SECRET_KEY os mesmos dados de acesso do MinIO neste exemplo
S3_COMPATIBLE_API_URL, S3_COMPATIBLE_PUBLIC_URL https://storage.exemplo.com
S3_COMPATIBLE_BUCKET unsend
API_RATE_LIMIT, AUTH_EMAIL_RATE_LIMIT 1, 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ínio Scheme Forward Hostname Porta Opções
send.exemplo.com http usesend_usesend 3000 Websockets Support, Block Common Exploits, certificado Let’s Encrypt e Force SSL
storage.exemplo.com http usesend_minio 9000 certificado 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.

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.

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.