nginx-proxy e Portainer: Múltiplas aplicações num só domínio

Gustavo Oliveira
4 min readApr 1, 2020

Quando trabalhei numa agência de desenvolvimento web existia a necessidade de testar as aplicações online e mostrá-las para os clientes. Como os projetos eram desenvolvidos em ambientes distintos (linguagem, banco, servidor, versão), surgiu o seguinte questionamento: como servir essas aplicações num só domínio?

Uma possibilidade é utilizar o docker. Dessa forma os ambientes ficam isolados em containers e podemos expor cada um em portas distintas do hospedeiro. No entanto, o roteamento através de portas é pouco prático, o ideal é utilizar um DNS e mapear cada aplicação para um subdomínio distinto. Para tal podemos utilizar um proxy reverso.

Proxy Reverso

O proxy reverso é um servidor tipicamente situado à frente de outros servidores que recebe as requisições, faz o roteamento para o servidor alvo e retorna a resposta para o cliente, podendo também aplicar funcionalidades como certificado SSL, load balancer e cache.

O NGINX pode ser configurado como proxy reverso e redirecionar as requisições para containers específicos. Esta configuração pode se tornar um pouco complexa, especialmente quando se utiliza certificado SSL. Somado a isso, quando um container é atualizado, é necessário atualizar também a configuração do NGINX, o que aumenta a chance de erros, além de consumir mais tempo. Neste artigo há um exemplo do passo a passo para esta configuração.

nginx-proxy

Um pacote bastante utilizado que abstrai e facilita a configuração e manutenção neste cenário é o nginx-proxy. Com apenas alguns parâmetros ele cria um container NGINX como proxy reverso e o recarrega quando há atualização em algum container alvo.

Para utilizar o nginx-proxy basta ter o docker instalado no servidor e executar o seguinte comando:

docker run -d -p 80:80 -v /var/run/docker.sock:/tmp/docker.sock:ro jwilder/nginx-proxy

Em seguida, cada container alvo deve possuir uma porta exposta para o hospedeiro e a variável de ambiente VIRTUAL_HOST que receberá o endereço da aplicação.

docker run -e VIRTUAL_HOST=app1.meusite.com …

Particularmente, eu prefiro utilizar o docker-compose, pois não preciso me preocupar em executar vários comandos longos, já que as definições dos containers ficam num arquivo conforme o exemplo abaixo em que utilizo um servidor com três aplicações:

  1. Apache + PHP
  2. PM2 + Node.js
  3. Tomcat + Java
version: '2'
services:
nginx-proxy:
image: jwilder/nginx-proxy
ports:
- "80:80"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
app1:
image: php:7.4-apache
environment:
- VIRTUAL_HOST=app1.meusite.com
app2:
image: keymetrics/pm2
environment:
- VIRTUAL_HOST=app2.meusite.com
app3:
image: tomcat
environment:
- VIRTUAL_HOST=app3.meusite.com

docker-letsencrypt-nginx-proxy-companion

Junto ao nginx-proxy é possível utilizar o pacote docker-letsencrypt-nginx-proxy-companion que lida com a criação automática, renovação e uso de certificados Let’s Encrypt nos containers alvos. Para utilizá-lo basta expor três novos volumes no container do nginx-proxy, adicionar o container do docker-letsencrypt-nginx-proxy-companion e a variável de ambiente LETSENCRYPT_HOST contendo o endereço da aplicação em cada container alvo.

version: '2'
services:
nginx-proxy:
container_name: nginx-proxy
image: jwilder/nginx-proxy
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- "/etc/nginx/vhost.d"
- "/usr/share/nginx/html"
- "/var/run/docker.sock:/tmp/docker.sock:ro"
- "/etc/nginx/certs"
letsencrypt-nginx-proxy-companion:
container_name: letsencrypt-nginx-proxy-companion
image: jrcs/letsencrypt-nginx-proxy-companion
restart: always
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
volumes_from:
- "nginx-proxy"
app1:
image: php:7.4-apache
environment:
- VIRTUAL_HOST=app1.meusite.cocm
- LETSENCRYPT_HOST=app1.meusite.com
app2:
image: keymetrics/pm2
environment:
- VIRTUAL_HOST=app2.meusite.com
- LETSENCRYPT_HOST=app2.meusite.com
app3:
image: tomcat
environment:
- VIRTUAL_HOST=app3.meusite.com
- LETSENCRYPT_HOST=app3.meusite.com

Portainer

Para facilitar o gerenciamento das aplicações, recomendo a utilização do Portainer. Ele provê uma interface gráfica organizada e prática para gerenciar containers, imagens, volumes, redes, stacks e configurações do docker. Você pode acessar os containers pelo navegador e fazer o controle de permissão de usuários, o que é bastante interessante no caso de uma agência web já que nem todos os usuários acessam o servidor diretamente, dominam docker ou devem ter controle total das aplicações.

Captura de tela do Portainer
Captura de tela da interface do Portainer

Para instalar o Portainer basta utilizar o exemplo abaixo e acessar o Portainer na porta 9000 do hospedeiro. Atualmente o Portainer não possui suporte para a versão 3 do docker-compose. No primeiro login você deverá definir uma senha, mas também é possível defini-la previamente conforme a documentação.

version: '2'services:  portainer:
image: portainer/portainer
command: -H unix:///var/run/docker.sock
restart: always
ports:
- 9000:9000
- 8000:8000
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
volumes:
portainer_data:

Nesta configuração o Portainer é acessado via HTTP. Para habilitar HTTPS você deve possuir um certificado. Segue um exemplo de como você pode gerar um certificado utilizando o OpenSSL.

$ openssl genrsa -out portainer.key 2048
$ openssl ecparam -genkey -name secp384r1 -out portainer.key
$ openssl req -new -x509 -sha256 -key portainer.key -out portainer.crt -days 3650

Utilize o exemplo abaixo para atrelar o certificado ao container do Portainer, sendo ~/local-certs o local onde se encontram o certificado (portainer.crt) e a chave (portainer.key) no hospedeiro. Há instruções para utilizar certificados gerados pelo Certbot na documentação.

version: '2'
services:
portainer:
image: portainer/portainer
restart: always
ports:
- 9000:9000
- 8000:8000
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ~/local-certs:/certs
- portainer_data:/data
command:
-H unix:///var/run/docker.sock
--ssl
--sslcert /certs/portainer.crt
--sslkey /certs/portainer.key
volumes:
portainer_data:

Conclusão

Agora você tem num mesmo servidor aplicações distintas isoladas em containers que podem ser acessados em subdomínios distintos via HTTPS, uma ferramenta web para gerenciá-las e controle de permissão de usuários.

--

--