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.
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á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.
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:
- constrói
docker/Dockerfile; - publica
ghcr.io/<dono-do-fork>/<nome-do-fork>:lateste uma tag imutável com SHA; - 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 dessh-keyscan -H SEU_HOSTobtido e verificado previamente;GHCR_USERNAMEeGHCR_READ_TOKEN: usuário GitHub e PAT clássicoread:packages;SWARM_SERVICE_NAME:usesend_usesendse a Stack se chamarusesend.
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.
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.