Deploy de Aplicações com Portainer e NGINX
![]() | Configurando um portainer + Docker + Nginx em uma VPS para acessar suas aplicações em produção com um gerenciador de containers poderoso. Para aqueles que querem ter um controle mais sofisticado em relação a alternativas como Caprover. |
Caso não tenha o Docker instalado na sua VPS
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
Instalar o docker
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin \
docker-compose-plugin
Instalando o portainer e NGINX Proxy Manager (NPM)
Crie um volume no docker para guardar os dados do portainer
docker volume create portainer_data
Crie um container do portainer e associe o volume a ele
docker run -d -p 8000:8000 -p 9443:9443 --name portainer --restart=always \
-v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:lts
Agora é possivel acessar o seu portainer no http://[SEU_IP]:9443 , perceba o https. Na primeira vez que acessar será solicitado a criação do seu usuário admin.
Agora vamos instalar o nosso gerenciador de proxy nginx para poder acessar o nosso portainer via dominio, mas antes vamos tentar acessar o nosso portainer pelo dominio.
Primeiro, dentro da sua VPS crie uma network para o nosso proxy e já conecta ela ao nosso portainer com os seguintes comandos:
docker network create nginx-proxy
docker network connect nginx-proxy portainer
Em seguida vamos acessar o painel do nosso portainer online e vamos criar uma "STACK" com a imagem do nginx proxy manager (NPM), no painel lateral esquerdo acesse stack e crie uma nova stack e em Web Editor adicione o seguinte código do docker-compose.yaml
version: '3'
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
restart: always
ports:
- '80:80'
- '81:81'
- '443:443'
volumes:
- nginx-proxy-manager-data:/data
- letsencrypt-certificates:/etc/letsencrypt
networks:
- nginx-proxy
networks:
nginx-proxy:
external: true # <- como criamos a network externamente via comando
volumes:
nginx-proxy-manager-data:
letsencrypt-certificates:
Salve ela com algum nome fácil, para facilitar usei nginx-proxy-manager
e quando ele estiver rodando acesse a sua VPS e tente conectar a sua network ao container do proxy, você perceberá que ele vai avisar que já está conectado.
docker network connect nginx-proxy nginx-proxy-manager
Agora com o proxy nginx rodando, você consegue acessar o painel com o http:[SEU_IP]:81 , perceba o http. Na primeira vez vai lhe ser solicitado os dados de login, por padrão o login é “admin” ou "[email protected]" e a senha é “changeme”. Assim que você se autenticar ele vai solicitar para que altere os dados, faça-o.
- login: admin ou [email protected]
- password: changeme
Agora antes de adicionar um dominio ao painel, vamos ter que apontar um subdominio para o IP da nossa VPS usando A Record, vamos usar por padrão portainer.[seudominio.com] por exemplo:
Type: A
Content: SEU_IP_DA_VPS
Name: portainer
Proxy: Desativado ← caso esteja na cloudflare
Agora acesse o painel do nginx http://SEU_IP:81 e configure o dominio no nginx-proxy-manager, nele podemos criar tanto o proxy quanto os certificados SSL.
Primeiro crie um novo host proxy e na aba DETALHES preencha assim
- Nomes de Domínio: portainer.[seudominio.com] - Este é o nome DNS do Registro A que você criou para o NGINX-Proxy-Manager
- Esquema ou Schema: Deixe como HTTP
- Host/IP de Encaminhamento ou Foward Host/IP: Esse é o nome do container, usaremos portainer, Como o Portainer e o nosso NGINX-Proxy-Manager estão na mesma rede Docker que configuramos lá atrás, poderemos usar o nosso proxy manager para vincular o dominio ao host do container. Lembre-se, o container alvo tem que tá na mesma network nginx-proxy que o proxy, assim o nosso proxy pode encontrar o container alvo.
- Porta: Usaremos 9443, a porta do container (Não é a porta exposta da aplicação e sim a porta do proprio container).
- Websockets support: Ligado
Agora na ABA SSL configure o SSL com o seguinte:
- Certificado SSL: Selecione a opção Solicitar um novo Certificado SSL
- Forçar SSL: Habilite nesse caso
- Suporte HTTP/2: Habilite nesse caso
- As outras opções vão depender do seu app
- No email: Insira seu endereço de e-mail para que o Let's Encrypt o avise quando seu certificado SSL estiver prestes a expirar. O NPM vai renovar seus certificados automaticamente, mas ainda é bom saber se seu certificado está prestes a expirar caso a renovação falhe.
- Você pode também fazer a mesma configuração para acessar o painel do nginx como estamos fazendo no portainer.
No final semelhante a isso:

Agora você poderá acessar o seu portainer pelo portainer.[seudominio.com] e não precisa usar a porta 9443, porque o NPM já resolverá isso para nós.
Instalando nosso próprio Docker Hub.
Vamos usar o docker registry para subir os deploys da nossa aplicação, já que a conta gratis do Docker Hub só permite 1 app privado, então não é interessante para quem quer produção.
Rode os seguintes comandos para garantir que o nosso registry seja criado e que esteja com autenticação simples, na sua VPS use os seguintes comandos.
sudo apt update
sudo apt install apache2-utils
Agora vamos gerar a autenticação no nosso registry com os comandos
mkdir -p ~/registry-auth
htpasswd -Bc ~/registry-auth/htpasswd admin
O "admin" é o nosso arquivo com o nome do usuário e as senhas que usaremos para acessar o container do registry.
Na sequencia vamos rodar o container do registry e já atribuir a ele a autenticação
docker run -d -p 5000:5000 --name registry \
--restart=always -v registry_data:/var/lib/registry \
-v ~/registry-auth:/auth \
-e "REGISTRY_AUTH=htpasswd" \
-e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" \
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" registry:2
Teste acessando dentro de sua VPS use
curl -u [seu_usuario_htpasswd]:[senha_do_seu_usuario_htpasswd] http://localhost:5000/v2/_catalog
você terá um resultado parecido com:
{"repositories":[]}
Agora você também pode configurar o registry no seu NPM, vá nas configurações de seu DNS e adicione o dominio que vai usar, vamos chamar de registry.[seudominio.com] nesse exemplo:
Primeiro crie um novo host proxy, lembre-se que o registry tem que estar na mesma network do proxy, você pode fazer isso direto no VPS com o comando
docker network connect nginx-proxy registry
ou no nosso portainer ao acessar o nosso container e configurar lá a network.

Agora no nosso NPM e na aba DETALHES preencha assim
- Nomes de Domínio: registry.[seudominio.com]
- Esquema: Deixe como HTTP
- Foward Host/IP: registry que é o nome do nosso container
- Porta: 5000.
- Websockets support: Desligado para esse exemplo
Agora na ABA SSL configure o SSL com o seguinte:
- Certificado SSL: Selecione a opção Solicitar um novo Certificado SSL
- Forçar SSL: Habilite nesse caso
- Suporte HTTP/2: Habilite nesse caso
- Insira seu email etc.
Pronto, dando tudo certo você agora pode logar na sua máquina local no nosso registry com
docker login registry.[seudominio.com]
Finalmente agora temos tudo pronto para prosseguir e subir nossa primeira aplicação no portainer. Para isso vamos subir um backend básico junto com um banco de dados postgres.
Deploy da primeira aplicação
Vamos primeiro criar a network database_net para conectar o nosso backend ao banco, você pode criar ela com o comando direto na vps ou no portainer nas opções de network e criar uma nova lá.
Primeiro vamos subir nosso banco de dados, para isso você pode usar algum template pronto do portainer, mas vamos usar uma config aqui criando uma STACK. No painel do portainer na barra lateral esquerda selecione STACK e na opção de nova stack e adicione o seguinte:
version: '3.8'
services:
postgres:
image: postgres:16.10
container_name: postgres # <- Para que o nome do container seja fixo
restart: always
environment:
POSTGRES_USER: seu_usuario_postgres # <- Editar aqui
POSTGRES_DB: nome_do_banco # <- Editar aqui
POSTGRES_PASSWORD: senha_do_banco # <- Editar aqui
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- database_net
command: ["postgres", "-c", "max_connections=100"]
volumes:
postgres_data:
name: postgres_data # <- Vamos dar o nome para que o volume tenha nome fixo
networks:
database_net:
external: true # <- apenas se ela já existir
Confirme e crie a sua stack do postgres
Subindo a primeira aplicação
O Nosso backend é em Nodejs, vamos subir um app com o seguinte arquivo Dockerfile.prod
FROM --platform=linux/arm64 node:20
# Caso a plataforma for ARM use o FROM acima
RUN mkdir /home/node/app
WORKDIR /home/node/app
COPY package*.json ./
RUN npm install
COPY . /home/node/app/
EXPOSE 80
CMD [ "npm", "run", "prod:migrations" ]
Estamos usando a porta 80 dentro do container mas no nosso container ao montar via compose precisaremos usar uma porta diferente para que não dê conflito, já que cada app precisa de uma porta diferente.
Com esse arquivo em mente dentro do diretorio raiz da nossa aplicação, vamos usar os comandos build do docker e push para enviar ao nosso registry.
Build
docker build -f ./docker/Dockerfile.prod -t registry.[seudominio.com]/seu_app_backend:latest .
Deploy
docker push registry.[seudominio.com]/seu_app_backend:latest
Tendo isso finalizado, vamos criar uma nova stack, dessa vez vamos acessar o nosso portainer e usar o seguinte arquivo yaml, vamos passar também
version: "3.8"
services:
seu_app_backend:
image: registry.[seudominio.com]/seu_app_backend:latest
container_name: seu_app_backend
restart: always
ports:
- 8888:80 # <- 8888 para export e que possa ser criado caso o container esteja na 80
environment:
- DEFAULT_ADMIN_USERNAME=${DEFAULT_ADMIN_USERNAME}
- DEFAULT_ADMIN_PASSWORD=${DEFAULT_ADMIN_PASSWORD}
- DB_HOST=${DB_HOST}
- DB_NAME=${DB_NAME}
- DB_PORT=${DB_PORT}
- DB_USER=${DB_USER}
- DB_PASSWORD=${DB_PASSWORD}
- DB_DIALECT=${DB_DIALECT}
- APP_HOST=${APP_HOST}
- APP_PORT=${APP_PORT}
- APP_PROD=${APP_PROD}
- APP_DEBUG=${APP_DEBUG}
- APP_SECRET_KEY=${APP_SECRET_KEY}
- JWT_SECRET_KEY=${JWT_SECRET_KEY}
networks:
- database_net # <- Para que o backend se comunique com o banco de dados
- nginx-proxy # <- para que possamos expor o nosso backend online
volumes:
- seu_app_backend_data:/home/node/app/src/public/uploads
networks:
database_net:
external: true
nginx-proxy:
external: true
volumes:
seu_app_backend_data:
name: seu_app_backend_data
Como pode ver esse arquivo permite passar variaveis de ambiente via portainer, já que nele também podemos usar elas, assim o nosso container sempre vai carregar elas.
Antes de confirmar passe as suas variaveis na configuração e finalize a criação do seu container, assim quando ele estiver online basta adicionar no Gerenciador DNS e seu NPM o seu novo host e pronto.
Agora no nosso NPM e na aba DETALHES preencha assim
- Nomes de Domínio: api.[seudominio.com]
- Esquema: Deixe como HTTP
- Foward Host/IP: seu_app_backend que é o nome do nosso container
- Porta: 8888.
- Websockets support: Desligado para esse exemplo
Agora na ABA SSL configure o SSL com o seguinte:
- Certificado SSL: Selecione a opção Solicitar um novo Certificado SSL
- Forçar SSL: Habilite nesse caso
- Suporte HTTP/2: Habilite nesse caso
- Insira seu email etc.
Agora seu app backend estará disponivel para ser acessado via https://api.[seudominio.com]
Fontes interessantes:
https://docs.portainer.io/start/install-ce/server/docker/linux
https://www.linode.com/community/questions/24444/how-do-i-install-nginx-proxy-manager-on-portainer
https://digitalinterativo.com.br/como-instalar-o-postgres-no-portainer/
https://medium.com/@psnavya90/deploying-a-simple-web-application-using-portainer-f280e61185d3
https://joshbuker.com/blog/using-portainer-and-github-for-continuous-deployment/