Como Usar o Traefik como um Proxy Reverso para Containers do Docker no Ubuntu 18.04

O autor selecionou o Girls Who Code para receber uma doação como parte do programa Write for DOnations.

Introdução

O Docker pode ser uma maneira eficiente de executar aplicativos web em produção, mas você pode querer executar vários aplicativos no mesmo host do Docker. Nesta situação, você precisará configurar um proxy reverso, já que você só deseja expor as portas 80 e 443 para o resto do mundo.

O Traefik é um proxy reverso que reconhece o Docker e inclui seu próprio painel de monitoramento ou dashboard. Neste tutorial, você usará o Traefik para rotear solicitações para dois containers de aplicação web diferentes: um container WordPress e um container Adminer, cada um falando com um banco de dados MySQL. Você irá configurar o Traefik para servir tudo através de HTTPS utilizando o Let’s Encrypt.

Pré-requisitos

Para acompanhar este tutorial, você vai precisar do seguinte:

Passo 1 — Configurando e Executando o Traefik

O projeto do Traefik tem uma imagem Docker oficial, portanto vamos utilizá-la para executar o Traefik em um container Docker.

Mas antes de colocarmos o nosso container Traefik em funcionamento, precisamos criar um arquivo de configuração e configurar uma senha criptografada para que possamos acessar o painel de monitoramento.

Usaremos o utilitário htpasswd para criar essa senha criptografada. Primeiro, instale o utilitário, que está incluído no pacote apache2-utils:

  • sudo apt-get install apache2-utils

Em seguida, gere a senha com o htpasswd. Substitua senha_segura pela senha que você gostaria de usar para o usuário admin do Traefik:

  • htpasswd -nb admin senha_segura

A saída do programa ficará assim:

Output
admin:$ apr1$ ruca84Hq$ mbjdMZBAG.KWn7vfN/SNK/

Você utilizará essa saída no arquivo de configuração do Traefik para configurar a Autenticação Básica de HTTP para a verificação de integridade do Traefik e para o painel de monitoramento. Copie toda a linha de saída para poder colá-la mais tarde.

Para configurar o servidor Traefik, criaremos um novo arquivo de configuração chamado traefik.toml usando o formato TOML. O TOML é uma linguagem de configuração semelhante ao arquivos INI, mas padronizado. Esse arquivo nos permite configurar o servidor Traefik e várias integrações, ou providers, que queremos usar. Neste tutorial, usaremos três dos provedores disponíveis do Traefik: api,docker e acme, que é usado para suportar o TLS utilizando o Let’s Encrypt.

Abra seu novo arquivo no nano ou no seu editor de textos favorito:

  • nano traefik.toml

Primeiro, adicione dois EntryPoints nomeados http ehttps, que todos os backends terão acesso por padrão:

traefik.toml
defaultEntryPoints = ["http", "https"] 

Vamos configurar os EntryPoints http e https posteriormente neste arquivo.

Em seguida, configure o provider api, que lhe dá acesso a uma interface do painel. É aqui que você irá colar a saída do comando htpasswd:

traefik.toml
 ... [entryPoints]   [entryPoints.dashboard]     address = ":8080"     [entryPoints.dashboard.auth]       [entryPoints.dashboard.auth.basic]         users = ["admin:sua_senha_criptografada"]  [api] entrypoint="dashboard" 

O painel é uma aplicação web separada que será executada no container do Traefik. Vamos definir o painel para executar na porta 8080.

A seção entrypoints.dashboard configura como nos conectaremos com o provider da api, e a seção entrypoints.dashboard.auth.basic configura a Autenticação Básica HTTP para o painel. Use a saída do comando htpasswd que você acabou de executar para o valor da entrada users. Você poderia especificar logins adicionais, separando-os com vírgulas.

Definimos nosso primeiro entryPoint, mas precisaremos definir outros para comunicação HTTP e HTTPS padrão que não seja direcionada para o provider da api. A seção entryPoints configura os endereços que o Traefik e os containers com proxy podem escutar. Adicione estas linhas ao arquivo logo abaixo do cabeçalho entryPoints:

traefik.toml
 ...   [entryPoints.http]     address = ":80"       [entryPoints.http.redirect]         entryPoint = "https"   [entryPoints.https]     address = ":443"       [entryPoints.https.tls] ... 

O entrypoint http manipula a porta 80, enquanto o entrypoint https usa a porta443 para o TLS/SSL. Redirecionamos automaticamente todo o tráfego na porta 80 para o entrypoint https para forçar conexões seguras para todas as solicitações.

Em seguida, adicione esta seção para configurar o suporte ao certificado Let’s Encrypt do Traefik:

traefik.toml
... [acme] email = "seu_email@seu_domínio" storage = "acme.json" entryPoint = "https" onHostRule = true   [acme.httpChallenge]   entryPoint = "http" 

Esta seção é chamada acme porque ACME é o nome do protocolo usado para se comunicar com o Let’s Encrypt para gerenciar certificados. O serviço Let’s Encrypt requer o registro com um endereço de e-mail válido, portanto, para que o Traefik gere certificados para nossos hosts, defina a chave email como seu endereço de e-mail. Em seguida, vamos especificar que armazenaremos as informações que vamos receber do Let’s Encrypt em um arquivo JSON chamado acme.json. A chave entryPoint precisa apontar para a porta de manipulação do entrypoint 443, que no nosso caso é o entrypoint https.

A chave onHostRule determina como o Traefik deve gerar certificados. Queremos buscar nossos certificados assim que nossos containers com os nomes de host especificados forem criados, e é isso que a configuração onHostRule fará.

A seção acme.httpChallenge nos permite especificar como o Let’s Encrypt pode verificar se o certificado deve ser gerado. Estamos configurando-o para servir um arquivo como parte do desafio através do entrypoint http.

Finalmente, vamos configurar o provider docker adicionando estas linhas ao arquivo:

traefik.toml
 ... [docker] domain = "seu_domínio" watch = true network = "web" 

O provedor docker permite que o Traefik atue como um proxy na frente dos containers do Docker. Configuramos o provider para vigiar ou watch por novos containers na rede web (que criaremos em breve) e os expor como subdomínios de seu_domínio.

Neste ponto, o traefik.toml deve ter o seguinte conteúdo:

traefik.toml
defaultEntryPoints = ["http", "https"]  [entryPoints]   [entryPoints.dashboard]     address = ":8080"     [entryPoints.dashboard.auth]       [entryPoints.dashboard.auth.basic]         users = ["admin:sua_senha_criptografada"]   [entryPoints.http]     address = ":80"       [entryPoints.http.redirect]         entryPoint = "https"   [entryPoints.https]     address = ":443"       [entryPoints.https.tls]  [api] entrypoint="dashboard"  [acme] email = "seu_email@seu_domínio" storage = "acme.json" entryPoint = "https" onHostRule = true   [acme.httpChallenge]   entryPoint = "http"  [docker] domain = "seu_domínio" watch = true network = "web" 

Salve o arquivo e saia do editor. Com toda essa configuração pronta, podemos ativar o Traefik.

Passo 2 – Executando o Container Traefik

Em seguida, crie uma rede do Docker para o proxy compartilhar com os containers. A rede do Docker é necessária para que possamos usá-la com aplicações que são executadas usando o Docker Compose. Vamos chamar essa rede de web.

  • docker network create web

Quando o container Traefik iniciar, nós o adicionaremos a essa rede. Em seguida, podemos adicionar containers adicionais a essa rede posteriormente para o Traefik fazer proxy.

Em seguida, crie um arquivo vazio que conterá as informações do Let’s Encrypt. Compartilharemos isso no container para que o Traefik possa usá-lo:

  • touch acme.json

O Traefik só poderá usar esse arquivo se o usuário root dentro do container tiver acesso exclusivo de leitura e gravação a ele. Para fazer isso, bloqueie as permissões em acme.json para que somente o proprietário do arquivo tenha permissão de leitura e gravação.

  • chmod 600 acme.json

Depois que o arquivo for repassado para o Docker, o proprietário será automaticamente alterado para o usuário root dentro do container.

Finalmente, crie o container Traefik com este comando:

  • docker run -d \
  • -v /var/run/docker.sock:/var/run/docker.sock \
  • -v $ PWD/traefik.toml:/traefik.toml \
  • -v $ PWD/acme.json:/acme.json \
  • -p 80:80 \
  • -p 443:443 \
  • -l traefik.frontend.rule=Host:monitor.seu_domínio \
  • -l traefik.port=8080 \
  • --network web \
  • --name traefik \
  • traefik:1.7.2-alpine

O comando é um pouco longo, então vamos dividi-lo. Usamos a flag -d para executar o container em segundo plano como um daemon. Em seguida, compartilhamos nosso arquivo docker.sock dentro do container para que o processo do Traefik possa escutar por alterações nos containers. Compartilhamos também o arquivo de configuração traefik.toml e o arquivoacme.json que criamos dentro do container.

Em seguida, mapeamos as portas :80 e :443 do nosso host Docker para as mesmas portas no container Traefik, para que o Traefik receba todo o tráfego HTTP e HTTPS para o servidor.

Em seguida, configuramos dois labels do Docker que informam ao Traefik para direcionar o tráfego para o monitor.seu_domínio para a porta :8080 dentro do container do Traefik, expondo o painel de monitoramento.

Configuramos a rede do container para web, e nomeamos o container para traefik.

Finalmente, usamos a imagem traefik:1.7.2-alpine para este container, porque é pequena.

Um ENTRYPOINT da imagem do Docker é um comando que sempre é executado quando um container é criado a partir da imagem. Neste caso, o comando é o binário traefik dentro do container. Você pode passar argumentos adicionais para esse comando quando você inicia o container, mas definimos todas as nossas configurações no arquivo traefik.toml.

Com o container iniciado, agora você tem um painel que você pode acessar para ver a integridade de seus containers. Você também pode usar este painel para visualizar os frontends e backends que o Traefik registrou. Acesse o painel de monitoramento apontando seu navegador para https://monitor.seu_domínio. Você será solicitado a fornecer seu nome de usuário e senha, que são admin e a senha que você configurou no Passo 1.

Uma vez logado, você verá uma interface semelhante a esta:

Ainda não há muito o que ver, mas deixe essa janela aberta e você verá o conteúdo mudar à medida que você adiciona containers para o Traefik trabalhar.

Agora temos nosso proxy Traefik em execução, configurado para funcionar com o Docker, e pronto para monitorar outros containers Docker. Vamos iniciar alguns containers para que o Traefik possa agir como proxy para eles.

Passo 3 — Registrando Containers com o Traefik

Com o container do Traefik em execução, você está pronto para executar aplicações por trás dele. Vamos lançar os seguintes containers por trás do Traefik:

  1. Um blog usando a imagem oficial do WordPress.

  2. Um servidor de gerenciamento de banco de dados usando a imagem oficial do Adminer.

Vamos gerenciar essas duas aplicações com o Docker Compose usando um arquivo docker-compose.yml. Abra o arquivo docker-compose.yml em seu editor:

  • nano docker-compose.yml

Adicione as seguintes linhas ao arquivo para especificar a versão e as redes que usaremos:

docker-compose.yml
  • version: "3"
  • networks:
  • web:
  • external: true
  • internal:
  • external: false

Usamos a versão 3 do Docker Compose porque é a mais nova versão principal do formato de arquivo Compose.

Para o Traefik reconhecer nossas aplicações, elas devem fazer parte da mesma rede e, uma vez que criamos a rede manualmente, nós a inserimos especificando o nome da rede web e configurandoexternal para true. Em seguida, definimos outra rede para que possamos conectar nossos containers expostos a um container de banco de dados que não vamos expor por meio do Traefik. Chamaremos essa rede de internal.

Em seguida, definiremos cada um dos nossos serviços ou services, um de cada vez. Vamos começar com o container blog, que basearemos na imagem oficial do WordPress. Adicione esta configuração ao arquivo:

docker-compose.yml
 version: "3" ...  services:   blog:     image: wordpress:4.9.8-apache     environment:       WORDPRESS_DB_PASSWORD:     labels:       - traefik.backend=blog       - traefik.frontend.rule=Host:blog.seu_domínio       - traefik.docker.network=web       - traefik.port=80     networks:       - internal       - web     depends_on:       - mysql 

A chave environment permite que você especifique variáveis de ambiente que serão definidas dentro do container. Ao não definir um valor para WORDPRESS_DB_PASSWORD, estamos dizendo ao Docker Compose para obter o valor de nosso shell e repassá-lo quando criamos o container. Vamos definir essa variável de ambiente em nosso shell antes de iniciar os containers. Dessa forma, não codificamos senhas no arquivo de configuração.

A seção labels é onde você especifica os valores de configuração do Traefik. As labels do Docker não fazem nada sozinhas, mas o Traefik as lê para saber como tratar os containers. Veja o que cada uma dessas labels faz:

  • traefik.backend especifica o nome do serviço de backend no Traefik (que aponta para o container real blog).

  • traefik.frontend.rule=Host:blog.seu_domínio diz ao Traefik para examinar o host solicitado e, se ele corresponde ao padrão de blog.seu_domínio, ele deve rotear o tráfego para o container blog.

  • traefik.docker.network=web especifica qual rede procurar sob o Traefik para encontrar o IP interno para esse container. Como o nosso container Traefik tem acesso a todas as informações do Docker, ele possivelmente levaria o IP para a rede internal se não especificássemos isso.

  • traefik.port especifica a porta exposta que o Traefik deve usar para rotear o tráfego para esse container.

Com essa configuração, todo o tráfego enviado para a porta 80 do host do Docker será roteado para o container blog.

Atribuímos este container a duas redes diferentes para que o Traefik possa encontrá-lo através da rede web e possa se comunicar com o container do banco de dados através da rede internal.

Por fim, a chave depends_on informa ao Docker Compose que este container precisa ser iniciado após suas dependências estarem sendo executadas. Como o WordPress precisa de um banco de dados para ser executado, devemos executar nosso container mysql antes de iniciar nosso containerblog.

Em seguida, configure o serviço MySQL adicionando esta configuração ao seu arquivo:

docker-compose.yml
 services: ...   mysql:     image: mysql:5.7     environment:       MYSQL_ROOT_PASSWORD:     networks:       - internal     labels:       - traefik.enable=false 

Estamos usando a imagem oficial do MySQL 5.7 para este container. Você notará que estamos mais uma vez usando um item environment sem um valor. As variáveis MYSQL_ROOT_PASSWORD eWORDPRESS_DB_PASSWORD precisarão ser configuradas com o mesmo valor para garantir que nosso container WordPress possa se comunicar com o MySQL. Nós não queremos expor o container mysql para o Traefik ou para o mundo externo, então estamos atribuindo este container apenas à rede internal. Como o Traefik tem acesso ao soquete do Docker, o processo ainda irá expor um frontend para o container mysql por padrão, então adicionaremos a label traefik.enable=false para especificar que o Traefik não deve expor este container.

Por fim, adicione essa configuração para definir o container do Adminer:

docker-compose.yml
 services: ...   adminer:     image: adminer:4.6.3-standalone     labels:       - traefik.backend=adminer       - traefik.frontend.rule=Host:db-admin.seu_domínio       - traefik.docker.network=web       - traefik.port=8080     networks:       - internal       - web     depends_on:       - mysql 

Este container é baseado na imagem oficial do Adminer. A configuração network e depends_on para este container corresponde exatamente ao que estamos usando para o container blog.

No entanto, como estamos redirecionando todo o tráfego para a porta 80 em nosso host Docker diretamente para o container blog, precisamos configurar esse container de forma diferente para que o tráfego chegue ao container adminer. A linha traefik.frontend.rule=Host:db-admin.seu_domínio diz ao Traefik para examinar o host solicitado. Se ele corresponder ao padrão do db-admin.seu_domínio, o Traefik irá rotear o tráfego para o container adminer.

Neste ponto, docker-compose.yml deve ter o seguinte conteúdo:

docker-compose.yml
 version: "3"  networks:   web:     external: true   internal:     external: false  services:   blog:     image: wordpress:4.9.8-apache     environment:       WORDPRESS_DB_PASSWORD:     labels:       - traefik.backend=blog       - traefik.frontend.rule=Host:blog.seu_domínio       - traefik.docker.network=web       - traefik.port=80     networks:       - internal       - web     depends_on:       - mysql   mysql:     image: mysql:5.7     environment:       MYSQL_ROOT_PASSWORD:     networks:       - internal     labels:       - traefik.enable=false   adminer:     image: adminer:4.6.3-standalone     labels:       - traefik.backend=adminer       - traefik.frontend.rule=Host:db-admin.seu_domínio       - traefik.docker.network=web       - traefik.port=8080     networks:       - internal       - web     depends_on:       - mysql 

Salve o arquivo e saia do editor de texto.

Em seguida, defina valores em seu shell para as variáveis WORDPRESS_DB_PASSWORD e MYSQL_ROOT_PASSWORD antes de iniciar seus containers:

  • export WORDPRESS_DB_PASSWORD=senha_segura_do_banco_de_dados
  • export MYSQL_ROOT_PASSWORD=senha_segura_do_banco_de_dados

Substitua senhaseguradobancodedados pela sua senha do banco de dados desejada. Lembre-se de usar a mesma senha tanto para `WORDPRESSDBPASSWORDquanto paraMYSQLROOT_PASSWORD`.

Com estas variáveis definidas, execute os containers usando o docker-compose:

  • docker-compose up -d

Agora, dê outra olhada no painel de administrador do Traefik. Você verá que agora existe um backend e um frontend para os dois servidores expostos:

Navegue até blog.seu_domínio, substituindo seu_domínio pelo seu domínio. Você será redirecionado para uma conexão TLS e poderá agora concluir a configuração do WordPress:

Agora acesse o Adminer visitando db-admin.seu_domínio no seu navegador, novamente substituindo seu_domínio pelo seu domínio. O container mysql não está exposto ao mundo externo, mas o container adminer tem acesso a ele através da rede internal do Docker que eles compartilham usando o nome do container mysql como um nome de host.

Na tela de login do Adminer, use o nome de usuário root, use mysql para o server, e use o valor que você definiu para MYSQL_ROOT_PASSWORD para a senha. Uma vez logado, você verá a interface de usuário do Adminer:

Ambos os sites agora estão funcionando, e você pode usar o painel em monitor.seu_domínio para ficar de olho em suas aplicações.

Conclusão

Neste tutorial, você configurou o Traefik para fazer proxy das solicitações para outras aplicações em containers Docker.

A configuração declarativa do Traefik no nível do container da aplicação facilita a configuração de mais serviços, e não há necessidade de reiniciar o container traefik quando você adiciona novas aplicações para fazer proxy, uma vez que o Traefik percebe as alterações imediatamente através do arquivo de soquete do Docker que ele está monitorando.

Para saber mais sobre o que você pode fazer com o Traefik, consulte a documentação oficial do Traefik.

Por Keith Thompson

DigitalOcean Community Tutorials

Davide Moro: High quality automated docker hub push using Github, TravisCI and pyup for Python tool distributions

Let’s say you want to distribute a Python tool with docker using known good dependency versions ready to be used by end users… In this article you will see how to continuously keeping up to date a Docker Hub container with minimal managing effort (because I’m a lazy guy) using github, TravisCI and pyup.

The goal was to reduce as much as possible any manual activity for updates, check all works fine before pushing, minimize build times and keep docker container always secure and updated with a final high quality confidence.

As an example let’s see what happens under the hood behind every pytest-play Docker Hub update on the official container https://cloud.docker.com/u/davidemoro/repository/docker/davidemoro/pytest-play (by the way if you are a pytest-play user: did you know that you can use Docker for running pytest-play and that there is a docker container ready to be used on Docker Hub? See a complete and working example here https://davidemoro.blogspot.com/2019/02/api-rest-testing-pytest-play-yaml-chuck-norris.html)

Repositories

The docker build/publish stuff lives on another repository, so https://github.com/davidemoro/pytest-play-docker is the repository that implements the Docker releasing workflow for https://github.com/pytest-dev/pytest-play on Docker Hub (https://hub.docker.com/r/davidemoro/pytest-play).

Workflow

This is the highly automated workflow at this time of writing for the pytest-play publishing on Docker Hub:

All tests executions run against the docker build so there is a warranty that what is pushed to Docker Hub works fine (it doesn’t check only that the build was successful but it runs integration tests against the docker build), so no versions incompatibilities, no integration issues between all the integrated third party pytest-play plugins and no issues due to the operative system integration (e.g., I recently experienced an issue on alpine linux with a pip install psycopg2-binary that apparently worked fine but if you try to import psycopg2 inside your code you get an unexpected import error due to a recent issue reported here https://github.com/psycopg/psycopg2/issues/684).

So now every time you run a command like the following one (see a complete and working example here https://davidemoro.blogspot.com/2019/02/api-rest-testing-pytest-play-yaml-chuck-norris.html):

docker run –rm -v $ (pwd):/src davidemoro/pytest-play

you know what was the workflow for every automated docker push for pytest-play.

Acknowledgements

Many thanks to Andrea Ratto for the 10 minutes travis build speedup due to Docker cache, from ~11 minutes to ~1 minute is a huge improvement indeed! It was possible thanks to the docker pull davidemoro/pytest-play command, the build with the –cache-from davidemoro/pytest-play option and running the longest steps in a separate and cacheable step (e.g., the very very long cassandra-driver compilation moved to requirements_cassandra.txt will be executed only if necessary).

Relevant technical details about pytest-play-docker follows (some minor optimizations are still possible saving in terms of final size).

pytest-play-docker/.travis.yml

sudo: required
services:
– docker
– …

env:
  global:
  – IMAGE_NAME=davidemoro/pytest-play
  – secure: …
before_script:
– …

script:
– travis_wait docker pull python:3.7
– travis_wait docker pull “$ IMAGE_NAME:latest”
– travis_wait 25 docker build –cache-from “$ IMAGE_NAME:latest” -t “$ IMAGE_NAME” .
– docker run -i –rm -v $ (pwd)/tests:/src –network host -v /var/run/mysqld/mysqld.sock:/var/run/mysqld/mysqld.sock $ IMAGE_NAME –splinter-webdriver=remote
  –splinter-remote-url=$ REMOTE_URL
deploy:
  provider: script
  script: bash docker_push
  on:
    branch: master

pytest-play-docker/docker_push

#!/bin/bash
echo “$ DOCKER_PASSWORD” | docker login -u “$ DOCKER_USERNAME” –password-stdin
docker tag “$ IMAGE_NAME” “$ IMAGE_NAME:$ TRAVIS_COMMIT”
docker tag “$ IMAGE_NAME” “$ IMAGE_NAME:latest”
docker push “$ IMAGE_NAME”:”$ TRAVIS_COMMIT”
docker push “$ IMAGE_NAME”:latest

Feedback

Any feedback will be always appreciated.

Do you like the Docker hub push process for pytest-play? Let me know becoming a pytest-play stargazer! Star
Planet Python

How To Use Traefik as a Reverse Proxy for Docker Containers on CentOS 7

The author selected Girls Who Code to receive a donation as part of the Write for DOnations program.

Introduction

Docker can be an efficient way to run web applications in production, but you may want to run multiple applications on the same Docker host. In this situation, you’ll need to set up a reverse proxy since you only want to expose ports 80 and 443 to the rest of the world.

Traefik is a Docker-aware reverse proxy that includes its own monitoring dashboard. In this tutorial, you’ll use Traefik to route requests to two different web application containers: a WordPress container and an Adminer container, each talking to a MySQL database. You’ll configure Traefik to serve everything over HTTPS using Let’s Encrypt.

Prerequisites

To follow along with this tutorial, you will need the following:

Step 1 — Configuring and Running Traefik

The Traefik project has an official Docker image, so we will use that to run Traefik in a Docker container.

But before we get our Traefik container up and running, we need to create a configuration file and set up an encrypted password so we can access the monitoring dashboard.

We’ll use the htpasswd utility to create this encrypted password. First, install the utility, which is included in the httpd-tools package:

  • sudo yum install -y httpd-tools

Then generate the password with htpasswd. Substitute secure_password with the password you’d like to use for the Traefik admin user:

  • htpasswd -nb admin secure_password

The output from the program will look like this:

Output
admin:$ apr1$ kEG/8JKj$ yEXj8vKO7HDvkUMI/SbOO.

You’ll use this output in the Traefik configuration file to set up HTTP Basic Authentication for the Traefik health check and monitoring dashboard. Copy the entire output line so you can paste it later.

To configure the Traefik server, we’ll create a new configuration file called traefik.toml using the TOML format. TOML is a configuration language similar to INI files, but standardized. This file lets us configure the Traefik server and various integrations, or providers, we want to use. In this tutorial, we will use three of Traefik’s available providers: api, docker, and acme, which is used to support TLS using Let’s Encrypt.

Open up your new file in Vi or your favorite text editor:

  • vi traefik.toml

Enter insert mode by pressing i, then add two named entry points, http and https, that all backends will have access to by default:

traefik.toml
defaultEntryPoints = ["http", "https"] 

We’ll configure the http and https entry points later in this file.

Next, configure the api provider, which gives you access to a dashboard interface. This is where you’ll paste the output from the htpasswd command:

traefik.toml
... [entryPoints]   [entryPoints.dashboard]     address = ":8080"     [entryPoints.dashboard.auth]       [entryPoints.dashboard.auth.basic]         users = ["admin:your_encrypted_password"]  [api] entrypoint="dashboard" 

The dashboard is a separate web application that will run within the Traefik container. We set the dashboard to run on port 8080.

The entrypoints.dashboard section configures how we’ll be connecting with the api provider, and the entrypoints.dashboard.auth.basic section configures HTTP Basic Authentication for the dashboard. Use the output from the htpasswd command you just ran for the value of the users entry. You could specify additional logins by separating them with commas.

We’ve defined our first entryPoint, but we’ll need to define others for standard HTTP and HTTPS communication that isn’t directed towards the api provider. The entryPoints section configures the addresses that Traefik and the proxied containers can listen on. Add these lines to the file underneath the entryPoints heading:

traefik.toml
...   [entryPoints.http]     address = ":80"       [entryPoints.http.redirect]         entryPoint = "https"   [entryPoints.https]     address = ":443"       [entryPoints.https.tls] ... 

The http entry point handles port 80, while the https entry point uses port 443 for TLS/SSL. We automatically redirect all of the traffic on port 80 to the https entry point to force secure connections for all requests.

Next, add this section to configure Let’s Encrypt certificate support for Traefik:

traefik.toml
... [acme] email = "your_email@your_domain" storage = "acme.json" entryPoint = "https" onHostRule = true   [acme.httpChallenge]   entryPoint = "http" 

This section is called acme because ACME is the name of the protocol used to communicate with Let’s Encrypt to manage certificates. The Let’s Encrypt service requires registration with a valid email address, so in order to have Traefik generate certificates for our hosts, set the email key to your email address. We then specify that we will store the information that we will receive from Let’s Encrypt in a JSON file called acme.json. The entryPoint key needs to point to the entry point handling port 443, which in our case is the https entry point.

The key onHostRule dictates how Traefik should go about generating certificates. We want to fetch our certificates as soon as our containers with specified hostnames are created, and that’s what the onHostRule setting will do.

The acme.httpChallenge section allows us to specify how Let’s Encrypt can verify that the certificate should be generated. We’re configuring it to serve a file as part of the challenge through the http entrypoint.

Finally, configure the docker provider by adding these lines to the file:

traefik.toml
... [docker] domain = "your_domain" watch = true network = "web" 

The docker provider enables Traefik to act as a proxy in front of Docker containers. We’ve configured the provider to watch for new containers on the web network (that we’ll create soon) and expose them as subdomains of your_domain.

At this point, traefik.toml should have the following contents:

traefik.toml
defaultEntryPoints = ["http", "https"]  [entryPoints]   [entryPoints.dashboard]     address = ":8080"     [entryPoints.dashboard.auth]       [entryPoints.dashboard.auth.basic]         users = ["admin:your_encrypted_password"]   [entryPoints.http]     address = ":80"       [entryPoints.http.redirect]         entryPoint = "https"   [entryPoints.https]     address = ":443"       [entryPoints.https.tls]  [api] entrypoint="dashboard"  [acme] email = "your_email@your_domain" storage = "acme.json" entryPoint = "https" onHostRule = true   [acme.httpChallenge]   entryPoint = "http"  [docker] domain = "your_domain" watch = true network = "web" 

Once you have added the contents, hit ESC to leave insert mode. Type :x then ENTER to save and exit the file. With all of this configuration in place, we can fire up Traefik.

Step 2 – Running the Traefik Container

Next, create a Docker network for the proxy to share with containers. The Docker network is necessary so that we can use it with applications that are run using Docker Compose. Let’s call this network web.

  • docker network create web

When the Traefik container starts, we will add it to this network. Then we can add additional containers to this network later for Traefik to proxy to.

Next, create an empty file which will hold our Let’s Encrypt information. We’ll share this into the container so Traefik can use it:

  • touch acme.json

Traefik will only be able to use this file if the root user inside of the container has unique read and write access to it. To do this, lock down the permissions on acme.json so that only the owner of the file has read and write permission.

  • chmod 600 acme.json

Once the file gets passed to Docker, the owner will automatically change to the root user inside the container.

Finally, create the Traefik container with this command:

  • docker run -d \
  • -v /var/run/docker.sock:/var/run/docker.sock \
  • -v $ PWD/traefik.toml:/traefik.toml \
  • -v $ PWD/acme.json:/acme.json \
  • -p 80:80 \
  • -p 443:443 \
  • -l traefik.frontend.rule=Host:monitor.your_domain \
  • -l traefik.port=8080 \
  • --network web \
  • --name traefik \
  • traefik:1.7.6-alpine

The command is a little long so let’s break it down.

We use the -d flag to run the container in the background as a daemon. We then share our docker.sock file into the container so that the Traefik process can listen for changes to containers. We also share the traefik.toml configuration file and the acme.json file we created into the container.

Next, we map ports 80 and 443 of our Docker host to the same ports in the Traefik container so Traefik receives all HTTP and HTTPS traffic to the server.

Then we set up two Docker labels that tell Traefik to direct traffic to the hostname monitor.your_domain to port 8080 within the Traefik container, exposing the monitoring dashboard.

We set the network of the container to web, and we name the container traefik.

Finally, we use the traefik:1.7.6-alpine image for this container, because it’s small.

A Docker image’s ENTRYPOINT is a command that always runs when a container is created from the image. In this case, the command is the traefik binary within the container. You can pass additional arguments to that command when you launch the container, but we’ve configured all of our settings in the traefik.toml file.

With the container started, you now have a dashboard you can access to see the health of your containers. You can also use this dashboard to visualize the frontends and backends that Traefik has registered. Access the monitoring dashboard by pointing your browser to https://monitor.your_domain. You will be prompted for your username and password, which are admin and the password you configured in Step 1.

Once logged in, you’ll see an interface similar to this:

Empty Traefik dashboard

There isn’t much to see just yet, but leave this window open, and you will see the contents change as you add containers for Traefik to work with.

We now have our Traefik proxy running, configured to work with Docker, and ready to monitor other Docker containers. Let’s start some containers for Traefik to act as a proxy for.

Step 3 — Registering Containers with Traefik

With the Traefik container running, you’re ready to run applications behind it. Let’s launch the following containers behind Traefik:

  1. A blog using the official WordPress image.
  2. A database management server using the official Adminer image.

We’ll manage both of these applications with Docker Compose using a docker-compose.yml file. Open the docker-compose.yml file in your editor:

  • vi docker-compose.yml

Add the following lines to the file to specify the version and the networks we’ll use:

docker-compose.yml
version: "3"  networks:   web:     external: true   internal:     external: false 

We use Docker Compose version 3 because it’s the newest major version of the Compose file format.

For Traefik to recognize our applications, they must be part of the same network, and since we created the network manually, we pull it in by specifying the network name of web and setting external to true. Then we define another network so that we can connect our exposed containers to a database container that we won’t expose through Traefik. We’ll call this network internal.

Next, we’ll define each of our services, one at a time. Let’s start with the blog container, which we’ll base on the official WordPress image. Add this configuration to the file:

docker-compose.yml
version: "3" ...  services:   blog:     image: wordpress:4.9.8-apache     environment:       WORDPRESS_DB_PASSWORD:     labels:       - traefik.backend=blog       - traefik.frontend.rule=Host:blog.your_domain       - traefik.docker.network=web       - traefik.port=80     networks:       - internal       - web     depends_on:       - mysql 

The environment key lets you specify environment variables that will be set inside of the container. By not setting a value for WORDPRESS_DB_PASSWORD, we’re telling Docker Compose to get the value from our shell and pass it through when we create the container. We will define this environment variable in our shell before starting the containers. This way we don’t hard-code passwords into the configuration file.

The labels section is where you specify configuration values for Traefik. Docker labels don’t do anything by themselves, but Traefik reads these so it knows how to treat containers. Here’s what each of these labels does:

  • traefik.backend specifies the name of the backend service in Traefik (which points to the actual blog container).
  • traefik.frontend.rule=Host:blog.your_domain tells Traefik to examine the host requested and if it matches the pattern of blog.your_domain it should route the traffic to the blog container.
  • traefik.docker.network=web specifies which network to look under for Traefik to find the internal IP for this container. Since our Traefik container has access to all of the Docker info, it would potentially take the IP for the internal network if we didn’t specify this.
  • traefik.port specifies the exposed port that Traefik should use to route traffic to this container.

With this configuration, all traffic sent to our Docker host’s port 80 will be routed to the blog container.

We assign this container to two different networks so that Traefik can find it via the web network and it can communicate with the database container through the internal network.

Lastly, the depends_on key tells Docker Compose that this container needs to start after its dependencies are running. Since WordPress needs a database to run, we must run our mysql container before starting our blog container.

Next, configure the MySQL service by adding this configuration to your file:

docker-compose.yml
services: ...   mysql:     image: mysql:5.7     environment:       MYSQL_ROOT_PASSWORD:     networks:       - internal     labels:       - traefik.enable=false 

We’re using the official MySQL 5.7 image for this container. You’ll notice that we’re once again using an environment item without a value. The MYSQL_ROOT_PASSWORD and WORDPRESS_DB_PASSWORD variables will need to be set to the same value to make sure that our WordPress container can communicate with MySQL. We don’t want to expose the mysql container to Traefik or the outside world, so we’re only assigning this container to the internal network. Since Traefik has access to the Docker socket, the process will still expose a frontend for the mysql container by default, so we’ll add the label traefik.enable=false to specify that Traefik should not expose this container.

Finally, add this configuration to define the Adminer container:

docker-compose.yml
services: ...   adminer:     image: adminer:4.6.3-standalone     labels:       - traefik.backend=adminer       - traefik.frontend.rule=Host:db-admin.your_domain       - traefik.docker.network=web       - traefik.port=8080     networks:       - internal       - web     depends_on:       - mysql 

This container is based on the official Adminer image. The network and depends_on configurations for this container exactly match what we’re using for the blog container.

However, since we’re directing all of the traffic to port 80 on our Docker host directly to the blog container, we need to configure this container differently in order for traffic to make it to our adminer container. The line traefik.frontend.rule=Host:db-admin.your_domain tells Traefik to examine the host requested. If it matches the pattern of db-admin.your_domain, Traefik will route the traffic to the adminer container.

At this point, docker-compose.yml should have the following contents:

docker-compose.yml
version: "3"  networks:   web:     external: true   internal:     external: false  services:   blog:     image: wordpress:4.9.8-apache     environment:       WORDPRESS_DB_PASSWORD:     labels:       - traefik.backend=blog       - traefik.frontend.rule=Host:blog.your_domain       - traefik.docker.network=web       - traefik.port=80     networks:       - internal       - web     depends_on:       - mysql   mysql:     image: mysql:5.7     environment:       MYSQL_ROOT_PASSWORD:     networks:       - internal     labels:       - traefik.enable=false   adminer:     image: adminer:4.6.3-standalone     labels:       - traefik.backend=adminer       - traefik.frontend.rule=Host:db-admin.your_domain       - traefik.docker.network=web       - traefik.port=8080     networks:       - internal       - web     depends_on:       - mysql 

Save the file and exit the text editor.

Next, set values in your shell for the WORDPRESS_DB_PASSWORD and MYSQL_ROOT_PASSWORD variables before you start your containers:

  • export WORDPRESS_DB_PASSWORD=secure_database_password
  • export MYSQL_ROOT_PASSWORD=secure_database_password

Substitute secure_database_password with your desired database password. Remember to use the same password for both WORDPRESS_DB_PASSWORD and MYSQL_ROOT_PASSWORD.

With these variables set, run the containers using docker-compose:

  • docker-compose up -d

Now take another look at the Traefik admin dashboard. You’ll see that there is now a backend and a frontend for the two exposed servers:

Populated Traefik dashboard

Navigate to blog.your_domain, substituting your_domain with your domain. You’ll be redirected to a TLS connection and can now complete the WordPress setup:

WordPress setup screen

Now access Adminer by visiting db-admin.your_domain in your browser, again substituting your_domain with your domain. The mysql container isn’t exposed to the outside world, but the adminer container has access to it through the internal Docker network that they share using the mysql container name as a host name.

On the Adminer login screen, use the username root, use mysql for the server, and use the value you set for MYSQL_ROOT_PASSWORD for the password. Once logged in, you’ll see the Adminer user interface:

Adminer connected to the MySQL database

Both sites are now working, and you can use the dashboard at monitor.your_domain to keep an eye on your applications.

Conclusion

In this tutorial, you configured Traefik to proxy requests to other applications in Docker containers.

Traefik’s declarative configuration at the application container level makes it easy to configure more services, and there’s no need to restart the traefik container when you add new applications to proxy traffic to since Traefik notices the changes immediately through the Docker socket file it’s monitoring.

To learn more about what you can do with Traefik, head over to the official Traefik documentation. If you’d like to explore Docker containers further, check out How To Set Up a Private Docker Registry on Ubuntu 18.04 or How To Secure a Containerized Node.js Application with Nginx, Let’s Encrypt, and Docker Compose. Although these tutorials are written for Ubuntu 18.04, many of the Docker-specific commands can be used for CentOS 7.

DigitalOcean Community Tutorials

Como Configurar um Registro Privado do Docker no Ubuntu 18.04

O autor selecionou a Apache Software Foundation para receber uma doação como parte do programa Write for DOnations

Introdução

O Registro Docker é uma aplicação que gerencia o armazenamento e a entrega de imagens de container do Docker. Os registros centralizam imagens de container e reduzem o tempo de criação para desenvolvedores. As imagens do Docker garantem o mesmo ambiente de runtime por meio da virtualização, mas a criação de uma imagem pode envolver um investimento de tempo significativo. Por exemplo, em vez de instalar dependências e pacotes separadamente para usar o Docker, os desenvolvedores podem baixar uma imagem compactada de um registro que contém todos os componentes necessários. Além disso, os desenvolvedores podem automatizar o envio de imagens para um registro usando ferramentas de integração contínua, tais como o TravisCI, para atualizar continuamente as imagens durante a produção e o desenvolvimento.

O Docker também tem um registro público gratuito, Docker Hub, que pode hospedar suas imagens personalizadas do Docker, mas há situações em que você não deseja que sua imagem fique disponível publicamente. As imagens geralmente contém todo o código necessário para executar uma aplicação, portanto, é preferível usar um registro privado ao usar um software proprietário.

Neste tutorial, você irá configurar e proteger seu próprio Registro Docker privado. Você irá usar o Docker Compose para definir configurações para executar suas aplicações Docker e o Nginx para encaminhar o tráfego do servidor de HTTPS para o container do Docker em execução. Depois de concluir este tutorial, você poderá enviar uma imagem do Docker personalizada para seu registro privado e baixar a imagem com segurança de um servidor remoto.

Pré-requisitos

Antes de iniciar este guia, você precisará do seguinte:

  • Dois servidores Ubuntu 18.04 configurados seguindo a Configuração Inicial de servidor com Ubuntu 18.04, incluindo um usuário sudo não-root e um firewall. Um servidor irá hospedar seu Registro Docker privado e o outro será o seu servidor cliente.

  • Docker e Docker-Compose instalados em ambos os servidores seguindo o tutorial How To Install Docker Compose on Ubuntu 18.04. Você só precisa concluir a primeira etapa deste tutorial para instalar o Docker Compose. Este tutorial explica como instalar o Docker como parte de seus pré-requisitos.

  • Nginx instalado no seu servidor de Registro Docker privado seguindo o tutoral Como Instalar o Nginx no Ubuntu 18.04.

  • Nginx protegido com o Let’s Encrypt em seu servidor de Registro Docker privado, seguindo o tutorial Como Proteger o Nginx com o Let’s Encrypt no Ubuntu 18.04. Certifique-se de redirecionar todo o tráfego de HTTP para HTTPS no Passo 4.

  • Um nome de domínio que resolve para o servidor que você está usando para o Registro de Docker privado. Você configurará isso como parte do pré-requisito para o Let’s Encrypt.

Passo 1 — Instalando e Configurando o Registro Docker

A ferramenta de linha de comando do Docker é útil para iniciar e gerenciar um ou dois containers Docker, mas, para um deployment completo, a maioria das aplicações em execução dentro de containers do Docker exige que outros componentes sejam executados em paralelo. Por exemplo, muitas aplicações web consistem em um servidor web, como o Nginx, que oferece e serve o código da aplicação, uma linguagem de script interpretada, como o PHP, e um servidor de banco de dados, como o MySQL.

Com o Docker Compose, você pode escrever um arquivo .yml para definir a configuração de cada container e as informações que os containers precisam para se comunicarem uns com os outros. Você pode usar a ferramenta de linha de comando docker-compose para emitir comandos para todos os componentes que compõem a sua aplicação.

O próprio Registro Docker é uma aplicação com vários componentes, portanto, você utilizará o Docker Compose para gerenciar sua configuração. Para iniciar uma instância do registro, você irá configurar um arquivo docker-compose.yml para definir o local onde seu registro armazenará seus dados.

No servidor que você criou para hospedar seu Registro Docker privado, você pode criar um diretório docker-registry, mover-se para ele, e criar uma subpasta data com os seguintes comandos:

  • mkdir ~/docker-registry && cd $ _
  • mkdir data

Use o seu editor de texto para criar o arquivo de configuração docker-compose.yml:

  • nano docker-compose.yml

Adicione o seguinte conteúdo ao arquivo, que descreve a configuração básica para o Registro Docker:

docker-compose.yml
version: '3'  services:   registry:     image: registry:2     ports:     - "5000:5000"     environment:       REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data     volumes:       - ./data:/data 

A seção environment define uma variável de ambiente no container do Registro Docker com o caminho /data. A aplicação do Registro Docker verifica essa variável de ambiente quando é inicializada e, como resultado, começa a salvar seus dados na pasta /data.

No entanto, como você incluiu a linha volumes: - ./data:/data, e o Docker irá começar a mapear o diretório /data daquele container para /data em seu servidor de registro. O resultado final é que os dados do Registro Docker são armazenados em ~/docker-registry/data no servidor do registro.

A seção ports, com a configuração 5000:5000, diz ao Docker para mapear a porta 5000 no servidor para a porta 5000 no container em execução. Isso lhe permite enviar uma solicitação para a porta 5000 no servidor, e ter essa solicitação encaminhada para a aplicação do registro.

Agora você pode iniciar o Docker Compose para verificar a configuração:

  • docker-compose up

Você verá barras de download em sua saída que mostram o Docker baixando a imagem do Registro Docker do próprio Docker Registry. Em um ou dois minutos, você verá uma saída semelhante à seguinte (as versões podem variar):

Output of docker-compose up
Starting docker-registry_registry_1 ... done Attaching to docker-registry_registry_1 registry_1 | time="2018-11-06T18:43:09Z" level=warning msg="No HTTP secret provided - generated random secret. This may cause problems with uploads if multiple registries are behind a load-balancer. To provide a shared secret, fill in http.secret in the configuration file or set the REGISTRY_HTTP_SECRET environment variable." go.version=go1.7.6 instance.id=c63483ee-7ad5-4205-9e28-3e809c843d42 version=v2.6.2 registry_1 | time="2018-11-06T18:43:09Z" level=info msg="redis not configured" go.version=go1.7.6 instance.id=c63483ee-7ad5-4205-9e28-3e809c843d42 version=v2.6.2 registry_1 | time="2018-11-06T18:43:09Z" level=info msg="Starting upload purge in 20m0s" go.version=go1.7.6 instance.id=c63483ee-7ad5-4205-9e28-3e809c843d42 version=v2.6.2 registry_1 | time="2018-11-06T18:43:09Z" level=info msg="using inmemory blob descriptor cache" go.version=go1.7.6 instance.id=c63483ee-7ad5-4205-9e28-3e809c843d42 version=v2.6.2 registry_1 | time="2018-11-06T18:43:09Z" level=info msg="listening on [::]:5000" go.version=go1.7.6 instance.id=c63483ee-7ad5-4205-9e28-3e809c843d42 version=v2.6.2

Você abordará a mensagem de aviso No HTTP secret provided posteriormente neste tutorial. A saída mostra que o container está iniciando. A última linha da saída mostra que ele começou a escutar com sucesso na porta 5000.

Por padrão, o Docker Compose permanecerá aguardando sua entrada, então pressione CTRL+C para encerrar seu container do Registro Docker.

Você configurou um Registro Docker completo, escutando na porta 5000. Nesse ponto, o registro não será iniciado, a menos que você o faça manualmente. Além disso, o Registro Docker não vem com nenhum mecanismo de autenticação integrado, por isso está atualmente inseguro e completamente aberto ao público. Nos passos quem seguem, você abordará essas preocupações de segurança.

Passo 2 — Configurando o Encaminhamento de Porta no Nginx

Você já tem HTTPS configurado em seu servidor de Registro Docker com Nginx, o que significa que agora você pode configurar o encaminhamento de porta do Nginx para a porta 5000. Depois de concluir esta etapa, você pode acessar seu registro diretamente em example.com.

Como parte do pré-requisito para o guia Como Proteger o Nginx com o Let’s Encrypt no Ubuntu 18.04, você já configurou o arquivo /etc/nginx/sites-available/example.com contendo a configuração do seu servidor.

Abra o arquivo com seu editor de texto:

sudo nano /etc/nginx/sites-available/example.com 

Encontre a linha location existente. Isso parecerá assim:

/etc/nginx/sites-available/example.com
... location / {   ... } ... 

Você precisa encaminhar o tráfego para a porta 5000, onde seu registro estará em execução. Você também deseja anexar cabeçalhos à solicitação para o registro, que fornecem informações adicionais do servidor com cada solicitação e resposta. Exclua o conteúdo da seção location e inclua o seguinte conteúdo nessa seção:

/etc/nginx/sites-available/example.com
... location / {     # Do not allow connections from docker 1.5 and earlier     # docker pre-1.6.0 did not properly set the user agent on ping, catch "Go *" user agents     if ($  http_user_agent ~ "^(docker\/1\.(3|4|5(?!\.[0-9]-dev))|Go ).*$  " ) {       return 404;     }      proxy_pass                          http://localhost:5000;     proxy_set_header  Host              $  http_host;   # required for docker client's sake     proxy_set_header  X-Real-IP         $  remote_addr; # pass on real client's IP     proxy_set_header  X-Forwarded-For   $  proxy_add_x_forwarded_for;     proxy_set_header  X-Forwarded-Proto $  scheme;     proxy_read_timeout                  900; } ... 

A seção $ http_user_agent verifica se a versão do Docker do cliente está acima de 1.5 e garante que o UserAgent não seja uma aplicação Go. Como você está usando a versão 2.0 do registro, os clientes mais antigos não são suportados. Para mais informações, você pode encontrar a configuração do cabeçalho do nginx em Docker’s Registry Nginx guide.

Salve e saia do arquivo. Aplique as alterações reiniciando o Nginx:

  • sudo service nginx restart

Você pode confirmar que o Nginx está encaminhando o tráfego para a porta 5000 executando o registro:

  • cd ~/docker-registry
  • docker-compose up

Em uma janela do navegador, abra a seguinte URL:

https://example.com/v2 

Você verá um objeto JSON vazio, ou:

{} 

No seu terminal, você verá uma saída semelhante à seguinte:

Output of docker-compose up
registry_1 | time="2018-11-07T17:57:42Z" level=info msg="response completed" go.version=go1.7.6 http.request.host=cornellappdev.com http.request.id=a8f5984e-15e3-4946-9c40-d71f8557652f http.request.method=GET http.request.remoteaddr=128.84.125.58 http.request.uri="/v2/" http.request.useragent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/604.4.7 (KHTML, like Gecko) Version/11.0.2 Safari/604.4.7" http.response.contenttype="application/json; charset=utf-8" http.response.duration=2.125995ms http.response.status=200 http.response.written=2 instance.id=3093e5ab-5715-42bc-808e-73f310848860 version=v2.6.2 registry_1 | 172.18.0.1 - - [07/Nov/2018:17:57:42 +0000] "GET /v2/ HTTP/1.0" 200 2 "" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/604.4.7 (KHTML, like Gecko) Version/11.0.2 Safari/604.4.7"

Você pode ver da última linha que uma requisição GET foi feita para /v2/, que é o endpoint para o qual você enviou uma solicitação do seu navegador. O container recebeu a solicitação que você fez, do encaminhamento de porta, e retornou uma resposta de {}. O código 200 na última linha da saída significa que o container tratou a solicitação com sucesso.

Agora que você configurou o encaminhamento de porta, é possível melhorar a segurança do seu registro.

Passo 3 — Configurando a Autenticação

Com o Nginx fazendo proxy das solicitações corretamente, agora você pode proteger seu registro com autenticação HTTP para gerenciar quem tem acesso ao seu Registro Docker. Para conseguir isso, você irá criar um arquivo de autenticação com o htpasswd e adicionará usuários a ele. A autenticação HTTP é rápida de configurar e segura em uma conexão HTTPS, que é o que o registro usará.

Você pode instalar o pacote htpasswd executando o seguinte:

  • sudo apt install apache2-utils

Agora você irá criar o diretório onde você armazenará nossas credenciais de autenticação e irá se mover para esse diretório. O $ _ expande para o último argumento do comando anterior, neste caso ~/docker-registry/auth:

  • mkdir ~/docker-registry/auth && cd $ _

Em seguida, você irá criar o primeiro usuário da seguinte forma, substituindo nome_usuário pelo nome de usuário que deseja usar. A flag -B especifica criptografia bcrypt, que é mais segura que a criptografia padrão. Digite a senha quando solicitado:

  • htpasswd -Bc registry.password nome_usuário

Nota: Para adicionar mais usuários execute novamente o comando anterior sem a opção -c (o c é para criar):

  • htpasswd registry.password nome_usuário

A seguir, você irá editar o arquivo docker-compose.yml para dizer ao Docker para usar o arquivo que você criou para autenticar usuários.

  • cd ~/docker-registry
  • nano docker-compose.yml

Você pode adicionar variáveis de ambiente e um volume para o diretório auth/ que você criou, editando o arquivo docker-compose.yml para informar ao Docker como você deseja autenticar usuários. Adicione o seguinte conteúdo destacado ao arquivo:

docker-compose.yml
version: '3'  services:   registry:     image: registry:2     ports:     - "5000:5000"     environment:       REGISTRY_AUTH: htpasswd       REGISTRY_AUTH_HTPASSWD_REALM: Registry       REGISTRY_AUTH_HTPASSWD_PATH: /auth/registry.password       REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data     volumes:       - ./auth:/auth       - ./data:/data 

Para REGISTRY_AUTH, você especificou htpasswd, que é o esquema de autenticação que você está utilizando, e definiu REGISTRY_AUTH_HTPASSWD_PATH para o caminho do arquivo de autenticação. Finalmente, REGISTRY_AUTH_HTPASSWD_REALM significa o nome do domínio do htpasswd.

Agora você pode verificar se sua autenticação funciona corretamente, executando o registro e verificando se ele solicita um nome de usuário e uma senha.

  • docker-compose up

Em uma janela do navegador, abra https://example.com/v2.

Depois de digitar o nome_usuário e a senha correspondente, você verá o {} mais uma vez. Você confirmou a configuração básica da autenticação: o registro só retornou o resultado depois que você digitou o nome de usuário e a senha corretos. Agora você já protegeu seu registro e pode continuar utilizando-o.

Passo 4 — Iniciando o Registro Docker como um Serviço

Você quer garantir que seu registro seja iniciado sempre que o sistema for inicializado. Se houver algum travamento imprevisto do sistema, você quer ter certeza de que o registro seja reiniciado quando o servidor for reinicializado. Abra o docker-compose.yml:

  • nano docker-compose.yml

Adicione a seguinte linha de conteúdo logo abaixo de registry::

docker-compose.yml
...   registry:     restart: always ... 

Você pode iniciar seu registro como um processo em segundo plano, o que permitirá que você saia da sessão ssh e persista o processo:

  • docker-compose up -d

Com o seu registro em execução em segundo plano, agora você pode preparar o Nginx para uploads de arquivos.

Passo 5 — Aumentando o Tamanho do Upload de Arquivos para o Nginx

Antes de poder enviar uma imagem para o registro, você precisa garantir que o registro possa lidar com grandes uploads de arquivos. Embora o Docker divida os uploads de imagens grandes em camadas separadas, às vezes elas podem ter mais de 1GB. Por padrão, o Nginx tem um limite de 1MB para uploads de arquivos, então você precisa editar o arquivo de configuração do nginx e configurar o tamanho máximo de upload do arquivo para 2GB.

  • sudo nano /etc/nginx/nginx.conf

Encontre a seção http e adicione a seguinte linha:

/etc/nginx/nginx.conf
... http {         client_max_body_size 2000M;         ... } ... 

Por fim, reinicie o Nginx para aplicar as alterações de configuração:

  • sudo service nginx restart

Agora você pode enviar imagens grandes para o seu Registro Docker sem erros no Nginx.

Passo 6 — Publicando em seu Registro Docker Privado

Agora você está pronto para publicar uma imagem no seu Registro Docker privado, mas primeiro é preciso criar uma imagem. Para este tutorial, você criará uma imagem simples baseada na imagem ubuntu do Docker Hub. O Docker Hub é um registro hospedado publicamente, com muitas imagens pré-configuradas que podem ser aproveitadas para “Dockerizar” rapidamente as aplicações. Usando a imagem ubuntu, você vai testar o envio e o download de imagens do seu registro.

Do seu servidor cliente, crie uma imagem pequena e vazia para enviar para o seu novo registro. As flags -i e -t lhe fornecem acesso interativo ao shell no container:

  • docker run -t -i ubuntu /bin/bash

Após o término do download, você estará dentro de um prompt do Docker, observe que o ID do container após root@ irá variar. Faça uma rápida mudança no sistema de arquivos criando um arquivo chamado SUCCESS. No próximo passo, você poderá usar esse arquivo para determinar se o processo de publicação foi bem-sucedido:

  • touch /SUCCESS

Saia do container do Docker:

  • exit

O comando a seguir cria uma nova imagem chamada test-image com base na imagem já em execução, além de todas as alterações que você fez. No nosso caso, a adição do arquivo /SUCCESS está incluída na nova imagem.

Faça o commit da alteração:

  • docker commit $ (docker ps -lq) test-image

Neste ponto, a imagem só existe localmente. Agora você pode enviá-la para o novo registro que você criou. Faça o login no seu Registro Docker:

  • docker login https://example.com

Digite o nome_usuário e a senha correspondente de antes. Em seguida, você colocará uma tag na imagem com a localização do registro privado para enviar a ele:

  • docker tag test-image example.com/test-image

Envie a imagem recém-marcada para o registro:

  • docker push example.com/test-image

Sua saída será semelhante à seguinte:

Output
The push refers to a repository [example.com/test-image] e3fbbfb44187: Pushed 5f70bf18a086: Pushed a3b5c80a4eba: Pushed 7f18b442972b: Pushed 3ce512daaf78: Pushed 7aae4540b42d: Pushed ...

Você verificou que seu registro trata a autenticação do usuário e permite que usuários autenticados enviem imagens ao registro. Em seguida, você confirmará que também é possível extrair ou baixar imagens do registro.

Passo 7 — Baixando de seu Registro Docker Privado

Retorne ao seu servidor de registro para que você possa testar o download da imagem a partir do seu servidor cliente. Também é possível testar isso a partir de um outro servidor.

Faça o login com o nome de usuário e senha que você configurou anteriormente:

  • docker login https://example.com

Agora você está pronto para baixar a imagem. Use seu nome de domínio e nome de imagem, que você marcou na etapa anterior:

  • docker login example.com/test-image

O Docker irá baixar a imagem e retornar você ao prompt. Se você executar a imagem no servidor de registro, verá que o arquivo SUCCESS criado anteriormente está lá:

  • docker run -it example.com/test-image /bin/bash

Liste seus arquivos dentro do shell bash:

  • ls

Você verá o arquivo SUCCESS que você criou para esta imagem:

SUCCESS  bin  boot  dev  etc  home  lib  lib64  media   mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var 

Você terminou de configurar um registro seguro para o qual os usuários podem enviar e baixar imagens personalizadas.

Conclusão

Neste tutorial, você configurou seu próprio Registro Docker privado e publicou uma imagem Docker. Como mencionado na introdução, você também pode usar o TravisCI ou uma ferramenta de CI semelhante para automatizar o envio diretamente para um registro privado. Ao aproveitar o Docker e os registros em seu fluxo de trabalho, você pode garantir que a imagem que contém o código resulte no mesmo comportamento em qualquer máquina, seja em produção ou em desenvolvimento. Para obter mais informações sobre como escrever arquivos do Docker, você pode ler o tutorial do Docker, que explica o processo.

Por Young Kim

DigitalOcean Community Tutorials

Como Construir uma Aplicação Node.js com o Docker

Introdução

A plataforma Docker permite aos desenvolvedores empacotar e executar aplicações como containers. Um container é um processo isolado que executa em um sistema operacional compartilhado, oferecendo uma alternativa mais leve às máquinas virtuais. Embora os containers não sejam novos, eles oferecem benefícios — incluindo isolamento do processo e padronização do ambiente — que estão crescendo em importância à medida que mais desenvolvedores usam arquiteturas de aplicativos distribuídos.

Ao criar e dimensionar uma aplicação com o Docker, o ponto de partida normalmente é a criação de uma imagem para a sua aplicação, que você pode então, executar em um container. A imagem inclui o código da sua aplicação, bibliotecas, arquivos de configuração, variáveis de ambiente, e runtime. A utilização de uma imagem garante que o ambiente em seu container está padronizado e contém somente o que é necessário para construir e executar sua aplicação.

Neste tutorial, você vai criar uma imagem de aplicação para um website estático que usa o framework Express e o Bootstrap. Em seguida, você criará um container usando essa imagem e a enviará para o Docker Hub para uso futuro. Por fim, você irá baixar a imagem armazenada do repositório do Docker Hub e criará outro container, demonstrando como você pode recriar e escalar sua aplicação.

Pré-requisitos

Para seguir este tutorial, você vai precisar de:

Passo 1 — Instalando as Dependências da Sua Aplicação

Para criar a sua imagem, primeiro você precisará produzir os arquivos de sua aplicação, que você poderá copiar para o seu container. Esses arquivos incluirão o conteúdo estático, o código e as dependências da sua aplicação.

Primeiro, crie um diretório para o seu projeto no diretório home do seu usuário não-root. Vamos chamar o nosso de node_project, mas sinta-se à vontade para substituir isso por qualquer outra coisa:

  • mkdir node_project

Navegue até esse diretório:

  • cd node_project

Esse será o diretório raiz do projeto:

Em seguida, crie um arquivo package.json com as dependências do seu projeto e outras informações de identificação. Abra o arquivo com o nano ou o seu editor favorito:

  • nano package.json

Adicione as seguintes informações sobre o projeto, incluindo seu nome, autor, licença, ponto de entrada e dependências. Certifique-se de substituir as informações do autor pelo seu próprio nome e seus detalhes de contato:

~/node_project/package.json
 {   "name": "nodejs-image-demo",   "version": "1.0.0",   "description": "nodejs image demo",   "author": "Sammy the Shark <sammy@example.com>",   "license": "MIT",   "main": "app.js",   "keywords": [     "nodejs",     "bootstrap",     "express"   ],   "dependencies": {     "express": "^4.16.4"   } } 

Este arquivo inclui o nome do projeto, autor e a licença sob a qual ele está sendo compartilhado. O npm recomenda manter o nome do seu projeto curto e descritivo, evitando duplicidades no registro npm. Listamos a licença do MIT no campo de licença, permitindo o uso e a distribuição gratuitos do código do aplicativo.

Além disso, o arquivo especifica:

  • "main": O ponto de entrada para a aplicação, app.js. Você criará esse arquivo em seguida.

  • "dependencies": As dependências do projeto — nesse caso, Express 4.16.4 ou acima.

Embora este arquivo não liste um repositório, você pode adicionar um seguindo estas diretrizes em adicionando um repositório ao seu arquivo package.json. Esse é um bom acréscimo se você estiver versionando sua aplicação.

Salve e feche o arquivo quando você terminar de fazer as alterações.

Para instalar as dependências do seu projeto, execute o seguinte comando:

  • npm install

Isso irá instalar os pacotes que você listou em seu arquivo package.json no diretório do seu projeto.

Agora podemos passar para a construção dos arquivos da aplicação.

Passo 2 — Criando os Arquivos da Aplicação

Vamos criar um site que oferece aos usuários informações sobre tubarões. Nossa aplicação terá um ponto de entrada principal, app.js, e um diretório views, que incluirá os recursos estáticos do projeto. A página inicial, index.html, oferecerá aos usuários algumas informações preliminares e um link para uma página com informações mais detalhadas sobre tubarões, sharks.html. No diretório views, vamos criar tanto a página inicial quanto sharks.html.

Primeiro, abra app.js no diretório principal do projeto para definir as rotas do projeto:

  • nano app.js

A primeira parte do arquivo irá criar a aplicação Express e os objetos Router, e definir o diretório base, a porta, e o host como variáveis:

~/node_project/app.js
 var express = require("express"); var app = express(); var router = express.Router();  var path = __dirname + '/views/'; const PORT = 8080; const HOST = '0.0.0.0'; 

A função require carrega o módulo express, que usamos então para criar os objetos app e router. O objeto router executará a função de roteamento do aplicativo e, como definirmos as rotas do método HTTP, iremos incluí-las nesse objeto para definir como nossa aplicação irá tratar as solicitações.

Esta seção do arquivo também define algumas variáveis, path, PORT, e HOST:

  • path: Define o diretório base, que será o subdiretório views dentro do diretório atual do projeto.

  • HOST: Define o endereço ao qual a aplicação se vinculará e escutará. Configurar isto para 0.0.0.0 ou todos os endereços IPv4 corresponde ao comportamento padrão do Docker de expor os containers para 0.0.0.0, a menos que seja instruído de outra forma.

  • PORT: Diz à aplicação para escutar e se vincular à porta 8080.

Em seguida, defina as rotas para a aplicação usando o objeto router:

~/node_project/app.js
 ...  router.use(function (req,res,next) {   console.log("/" + req.method);   next(); });  router.get("/",function(req,res){   res.sendFile(path + "index.html"); });  router.get("/sharks",function(req,res){   res.sendFile(path + "sharks.html"); }); 

A função router.use carrega uma função de middleware que registrará as solicitações do roteador e as transmitirá para as rotas da aplicação. Estas são definidas nas funções subsequentes, que especificam que uma solicitação GET para a URL base do projeto deve retornar a página index.html, enquanto uma requisição GET para a rota /sharks deve retornar sharks.html.

Finalmente, monte o middleware router e os recursos estáticos da aplicação e diga à aplicação para escutar na porta 8080:

~/node_project/app.js
 ...  app.use(express.static(path)); app.use("/", router);  app.listen(8080, function () {   console.log('Example app listening on port 8080!') }) 

O arquivo app.js finalizado ficará assim:

~/node_project/app.js
 var express = require("express"); var app = express(); var router = express.Router();  var path = __dirname + '/views/'; const PORT = 8080; const HOST = '0.0.0.0';  router.use(function (req,res,next) {   console.log("/" + req.method);   next(); });  router.get("/",function(req,res){   res.sendFile(path + "index.html"); });  router.get("/sharks",function(req,res){   res.sendFile(path + "sharks.html"); });  app.use(express.static(path)); app.use("/", router);  app.listen(8080, function () {   console.log('Example app listening on port 8080!') }) 

Salve e feche o arquivo quando tiver terminado.

Em seguida, vamos adicionar algum conteúdo estático à aplicação. Comece criando o diretório views:

  • mkdir views

Abra a página inicial, index.html:

  • nano views/index.html

Adicione o seguinte código ao arquivo, que irá importar o Bootstrap e criar o componente jumbotron com um link para a página de informações mais detalhadas sharks.html

~/node_project/views/index.html
 <!DOCTYPE html> <html lang="en">  <head>     <title>About Sharks</title>     <meta charset="utf-8">     <meta name="viewport" content="width=device-width, initial-scale=1">     <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">     <link href="css/styles.css" rel="stylesheet">     <link href="https://fonts.googleapis.com/css?family=Merriweather:400,700" rel="stylesheet" type="text/css"> </head>  <body>     <nav class="navbar navbar-dark navbar-static-top navbar-expand-md">         <div class="container">             <button type="button" class="navbar-toggler collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span>             </button> <a class="navbar-brand" href="#">Everything Sharks</a>             <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">                 <ul class="nav navbar-nav mr-auto">                     <li class="active nav-item"><a href="/" class="nav-link">Home</a>                     </li>                     <li class="nav-item"><a href="/sharks" class="nav-link">Sharks</a>                     </li>                 </ul>             </div>         </div>     </nav>     <div class="jumbotron">         <div class="container">             <h1>Want to Learn About Sharks?</h1>             <p>Are you ready to learn about sharks?</p>             <br>             <p><a class="btn btn-primary btn-lg" href="/sharks" role="button">Get Shark Info</a>             </p>         </div>     </div>     <div class="container">         <div class="row">             <div class="col-lg-6">                 <h3>Not all sharks are alike</h3>                 <p>Though some are dangerous, sharks generally do not attack humans. Out of the 500 species known to researchers, only 30 have been known to attack humans.                 </p>             </div>             <div class="col-lg-6">                 <h3>Sharks are ancient</h3>                 <p>There is evidence to suggest that sharks lived up to 400 million years ago.                 </p>             </div>         </div>     </div> </body>  </html> 

A navbar de nível superior aqui, permite que os usuários alternem entre as páginas Home e Sharks. No subcomponente navbar-nav, estamos utilizando a classe active do Bootstrap para indicar a página atual ao usuário. Também especificamos as rotas para nossas páginas estáticas, que correspondem às rotas que definimos em app.js:

~/node_project/views/index.html
 ... <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">    <ul class="nav navbar-nav mr-auto">       <li class="active nav-item"><a href="/" class="nav-link">Home</a>       </li>       <li class="nav-item"><a href="/sharks" class="nav-link">Sharks</a>       </li>    </ul> </div> ... 

Além disso, criamos um link para nossa página de informações sobre tubarões no botão do nosso jumbotron:

~/node_project/views/index.html
 ... <div class="jumbotron">    <div class="container">       <h1>Want to Learn About Sharks?</h1>       <p>Are you ready to learn about sharks?</p>       <br>       <p><a class="btn btn-primary btn-lg" href="/sharks" role="button">Get Shark Info</a>       </p>    </div> </div> ... 

Há também um link para uma folha de estilo personalizada no cabeçalho:

~/node_project/views/index.html
... <link href="css/styles.css" rel="stylesheet"> ... 

Vamos criar esta folha de estilo no final deste passo.

Salve e feche o arquivo quando terminar.

Com a página inicial da aplicação funcionando, podemos criar nossa página de informações sobre tubarões, sharks.html, que oferecerá aos usuários interessados mais informações sobre os tubarões.

Abra o arquivo:

  • nano views/sharks.html

Adicione o seguinte código, que importa o Bootstrap e a folha de estilo personalizada, e oferece aos usuários informações detalhadas sobre determinados tubarões:

~/node_project/views/sharks.html
<!DOCTYPE html> <html lang="en">  <head>     <title>About Sharks</title>     <meta charset="utf-8">     <meta name="viewport" content="width=device-width, initial-scale=1">     <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">     <link href="css/styles.css" rel="stylesheet">     <link href="https://fonts.googleapis.com/css?family=Merriweather:400,700" rel="stylesheet" type="text/css"> </head> <nav class="navbar navbar-dark navbar-static-top navbar-expand-md">     <div class="container">         <button type="button" class="navbar-toggler collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span>         </button> <a class="navbar-brand" href="/">Everything Sharks</a>         <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">             <ul class="nav navbar-nav mr-auto">                 <li class="nav-item"><a href="/" class="nav-link">Home</a>                 </li>                 <li class="active nav-item"><a href="/sharks" class="nav-link">Sharks</a>                 </li>             </ul>         </div>     </div> </nav> <div class="jumbotron text-center">     <h1>Shark Info</h1> </div> <div class="container">     <div class="row">         <div class="col-lg-6">             <p>                 <div class="caption">Some sharks are known to be dangerous to humans, though many more are not. The sawshark, for example, is not considered a threat to humans.                 </div>                 <img src="https://assets.digitalocean.com/articles/docker_node_image/sawshark.jpg" alt="Sawshark">             </p>         </div>         <div class="col-lg-6">             <p>                 <div class="caption">Other sharks are known to be friendly and welcoming!</div>                 <img src="https://assets.digitalocean.com/articles/docker_node_image/sammy.png" alt="Sammy the Shark">             </p>         </div>     </div> </div>  </html> 

Observe que neste arquivo, usamos novamente a classe active para indicar a página atual.

Salve e feche o arquivo quando tiver terminado.

Finalmente, crie a folha de estilo CSS personalizada que você vinculou em index.html e sharks.html criando primeiro uma pasta css no diretório views:

  • mkdir views/css

Abra a folha de estilo:

  • nano views/css/styles.css

Adicione o seguinte código, que irá definir a cor desejada e a fonte para nossas páginas:

~/node_project/views/css/styles.css
 .navbar {     margin-bottom: 0;     background: #000000; }  body {     background: #000000;     color: #ffffff;     font-family: 'Merriweather', sans-serif; }  h1, h2 {     font-weight: bold; }  p {     font-size: 16px;     color: #ffffff; }  .jumbotron {     background: #0048CD;     color: white;     text-align: center; }  .jumbotron p {     color: white;     font-size: 26px; }  .btn-primary {     color: #fff;     text-color: #000000;     border-color: white;     margin-bottom: 5px; }  img, video, audio {     margin-top: 20px;     max-width: 80%; }  div.caption: {     float: left;     clear: both; } 

Além de definir a fonte e a cor, esse arquivo também limita o tamanho das imagens especificando max-width ou largura máxima de 80%. Isso evitará que ocupem mais espaço do que gostaríamos na página.

Salve e feche o arquivo quando tiver terminado.

Com os arquivos da aplicação no lugar e as dependências do projeto instaladas, você está pronto para iniciar a aplicação.

Se você seguiu o tutorial de configuração inicial do servidor nos pré-requisitos, você terá um firewall ativo que permita apenas o tráfego SSH. Para permitir o tráfego para a porta 8080, execute:

  • sudo ufw allow 8080

Para iniciar a aplicação, certifique-se de que você está no diretório raiz do seu projeto:

  • cd ~/node_project

Inicie sua aplicação com node app.js:

  • node app.js

Dirija seu navegador para http://ip_do_seu_servidor:8080. Você verá a seguinte página inicial:

Clique no botão Get Shark Info. Você verá a seguinte página de informações:

Agora você tem uma aplicação instalada e funcionando. Quando estiver pronto, saia do servidor digitando CTRL + C. Agora podemos passar a criar o Dockerfile que nos permitirá recriar e escalar essa aplicação conforme desejado.

Step 3 — Escrevendo o Dockerfile

Seu Dockerfile especifica o que será incluído no container de sua aplicação quando for executado. A utilização de um Dockerfile permite que você defina seu ambiente de container e evite discrepâncias com dependências ou versões de runtime.

Seguindo estas diretrizes na construção de containers otimizados, vamos tornar nossa imagem o mais eficiente possível, minimizando o número de camadas de imagem e restringindo a função da imagem a uma única finalidade — recriar nossos arquivos da aplicação e o conteúdo estático.

No diretório raiz do seu projeto, crie o Dockerfile:

  • nano Dockerfile

As imagens do Docker são criadas usando uma sucessão de imagens em camadas que são construídas umas sobre as outras. Nosso primeiro passo será adicionar a imagem base para a nossa aplicação que formará o ponto inicial da construção da aplicação.

Vamos utilizar a imagem node:10, uma vez que, no momento da escrita desse tutorial, esta é a versão LTS reomendada do Node.js. Adicione a seguinte instrução FROM para definir a imagem base da aplicação:

~/node_project/Dockerfile
FROM node:10 

Esta imagem inclui Node.js e npm. Cada Dockerfile deve começar com uma instrução FROM.

Por padrão, a imagem Node do Docker inclui um usuário não-root node que você pode usar para evitar a execução de seu container de aplicação como root. Esta é uma prática de segurança recomendada para evitar executar containers como root e para restringir recursos dentro do container para apenas aqueles necessários para executar seus processos. Portanto, usaremos o diretório home do usuário node como o diretório de trabalho de nossa aplicação e o definiremos como nosso usuário dentro do container. Para mais informações sobre as melhores práticas ao trabalhar com a imagem Node do Docker, veja este guia de melhores práticas.

Para um ajuste fino das permissões no código da nossa aplicação no container, vamos criar o subdiretório node_modules em /home/node juntamente com o diretório app. A criação desses diretórios garantirá que eles tenham as permissões que desejamos, o que será importante quando criarmos módulos de node locais no container com npm install. Além de criar esses diretórios, definiremos a propriedade deles para o nosso usuário node:

~/node_project/Dockerfile
... RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app 

Para obter mais informações sobre o utilitário de consolidação das instruções RUN, veja esta discussão sobre como gerenciar camadas de container.

Em seguida, defina o diretório de trabalho da aplicação para /home/node/app:

~/node_project/Dockerfile
... WORKDIR /home/node/app 

Se WORKDIR não estiver definido, o Docker irá criar um por padrão, então é uma boa ideia defini-lo explicitamente.

A seguir, copie os arquivos package.json e package-lock.json (para npm 5+):

~/node_project/Dockerfile
... COPY package*.json ./ 

Adicionar esta instrução COPY antes de executar o npm install ou copiar o código da aplicação nos permite aproveitar o mecanismo de armazenamento em cache do Docker. Em cada estágio da compilação ou build, o Docker verificará se há uma camada armazenada em cache para essa instrução específica. Se mudarmos o package.json, esta camada será reconstruída, mas se não o fizermos, esta instrução permitirá ao Docker usar a camada de imagem existente e ignorar a reinstalação dos nossos módulos de node.

Depois de copiar as dependências do projeto, podemos executar npm install:

~/node_project/Dockerfile
... RUN npm install 

Copie o código de sua aplicação para o diretório de trabalho da mesma no container:

~/node_project/Dockerfile
... COPY . . 

Para garantir que os arquivos da aplicação sejam de propriedade do usuário não-root node, copie as permissões do diretório da aplicação para o diretório no container:

~/node_project/Dockerfile
... COPY --chown=node:node . . 

Defina o usuário para node:

~/node_project/Dockerfile
... USER node 

Exponha a porta 8080 no container e inicie a aplicação:

~/node_project/Dockerfile
... EXPOSE 8080  CMD [ "node", "app.js" ] 

EXPOSE não publica a porta, mas funciona como uma maneira de documentar quais portas no container serão publicadas em tempo de execução. CMD executa o comando para iniciar a aplicação – neste caso, node app.js. Observe que deve haver apenas uma instrução CMD em cada Dockerfile. Se você incluir mais de uma, somente a última terá efeito.

Há muitas coisas que você pode fazer com o Dockerfile. Para obter uma lista completa de instruções, consulte a documentação de referência Dockerfile do Docker

O Dockerfile completo estará assim:

~/node_project/Dockerfile
 FROM node:10  RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app  WORKDIR /home/node/app  COPY package*.json ./  RUN npm install  COPY . .  COPY --chown=node:node . .  USER node  EXPOSE 8080  CMD [ "node", "app.js" ] 

Salve e feche o arquivo quando terminar a edição.

Antes de construir a imagem da aplicação, vamos adicionar um arquivo .dockerignore. Trabalhando de maneira semelhante a um arquivo .gitignore, .dockerignore especifica quais arquivos e diretórios no diretório do seu projeto não devem ser copiados para o seu container.

Abra o arquivo .dockerignore:

  • nano .dockerignore

Dentro do arquivo, adicione seus módulos de node, logs npm, Dockerfile, e o arquivo .dockerignore:

~/node_project/.dockerignore
node_modules npm-debug.log Dockerfile .dockerignore 

Se você estiver trabalhando com o Git, então você também vai querer adicionar o seu diretório .git e seu arquivo .gitignore.

Salve e feche o arquivo quando tiver terminado.

Agora você está pronto para construir a imagem da aplicação usando o comando docker build. Usar a flag -t com o docker build permitirá que você marque a imagem com um nome memorizável. Como vamos enviar a imagem para o Docker Hub, vamos incluir nosso nome de usuário do Docker Hub na tag. Vamos marcar a imagem como nodejs-image-demo, mas sinta-se à vontade para substituir isto por um nome de sua escolha. Lembre-se também de substituir seu_usuário_dockerhub pelo seu nome real de usuário do Docker Hub:

  • docker build -t seu_usuário_dockerhub/nodejs-image-demo .

O . especifica que o contexto do build é o diretório atual.

Levará um ou dois minutos para construir a imagem. Quando estiver concluído, verifique suas imagens:

  • docker images

Você verá a seguinte saída:

Output
REPOSITORY TAG IMAGE ID CREATED SIZE seu_usuário_dockerhub/nodejs-image-demo latest 1c723fb2ef12 8 seconds ago 895MB node 10 f09e7c96b6de 17 hours ago 893MB

É possível criar um container com essa imagem usando docker run. Vamos incluir três flags com esse comando:

  • -p: Isso publica a porta no container e a mapeia para uma porta em nosso host. Usaremos a porta 80 no host, mas sinta-se livre para modificá-la, se necessário, se tiver outro processo em execução nessa porta. Para obter mais informações sobre como isso funciona, consulte esta discussão nos documentos do Docker sobre port binding.

  • -d: Isso executa o container em segundo plano.

  • --name: Isso nos permite dar ao container um nome memorizável.

Execute o seguinte comando para construir o container:

  • docker run --name nodejs-image-demo -p 80:8080 -d seu_usuário_dockerhub/nodejs-image-demo

Depois que seu container estiver em funcionamento, você poderá inspecionar uma lista de containers em execução com docker ps:

  • docker ps

Você verá a seguinte saída:

Output
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e50ad27074a7 seu_usuário_dockerhub/nodejs-image-demo "node app.js" 8 seconds ago Up 7 seconds 0.0.0.0:80->8080/tcp nodejs-image-demo

Com seu container funcionando, você pode visitar a sua aplicação apontando seu navegador para http://ip_do_seu_servidor. Você verá a página inicial da sua aplicação novamente:

Agora que você criou uma imagem para sua aplicação, você pode enviá-la ao Docker Hub para uso futuro.

Passo 4 — Usando um Repositório para Trabalhar com Imagens

Ao enviar sua imagem de aplicação para um registro como o Docker Hub, você a torna disponível para uso subsequente à medida que cria e escala seus containers. Vamos demonstrar como isso funciona, enviando a imagem da aplicação para um repositório e, em seguida, usando a imagem para recriar nosso container.

A primeira etapa para enviar a imagem é efetuar login na conta do Docker Hub que você criou nos pré-requisitos:

  • docker login -u seu_usuário_dockerhub -p senha_do_usuário_dockerhub

Efetuando o login dessa maneira será criado um arquivo ~/.docker/config.json no diretório home do seu usuário com suas credenciais do Docker Hub.

Agora você pode enviar a imagem da aplicação para o Docker Hub usando a tag criada anteriormente, seu_usuário_dockerhub/nodejs-image-demo:

  • docker push seu_usuário_dockerhub/nodejs-image-demo

Vamos testar o utilitário do registro de imagens destruindo nosso container e a imagem de aplicação atual e reconstruindo-os com a imagem em nosso repositório.

Primeiro, liste seus containers em execução:

  • docker ps

Você verá a seguinte saída:

Output
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e50ad27074a7 seu_usuário_dockerhub/nodejs-image-demo "node app.js" 3 minutes ago Up 3 minutes 0.0.0.0:80->8080/tcp nodejs-image-demo

Usando o CONTAINER ID listado em sua saída, pare o container da aplicação em execução. Certifique-se de substituir o ID destacado abaixo por seu próprio CONTAINER ID:

  • docker stop e50ad27074a7

Liste todas as suas imagens com a flag -a:

  • docker images -a

Você verá a seguinte saída com o nome da sua imagem, seuusuáriodockerhub/nodejs-image-demo, juntamente com a imagem node e outras imagens do seu build.

Output
REPOSITORY TAG IMAGE ID CREATED SIZE seu_usuário_dockerhub/nodejs-image-demo latest 1c723fb2ef12 7 minutes ago 895MB <none> <none> e039d1b9a6a0 7 minutes ago 895MB <none> <none> dfa98908c5d1 7 minutes ago 895MB <none> <none> b9a714435a86 7 minutes ago 895MB <none> <none> 51de3ed7e944 7 minutes ago 895MB <none> <none> 5228d6c3b480 7 minutes ago 895MB <none> <none> 833b622e5492 8 minutes ago 893MB <none> <none> 5c47cc4725f1 8 minutes ago 893MB <none> <none> 5386324d89fb 8 minutes ago 893MB <none> <none> 631661025e2d 8 minutes ago 893MB node 10 f09e7c96b6de 17 hours ago 893MB

Remova o container parado e todas as imagens, incluindo imagens não utilizadas ou pendentes, com o seguinte comando:

  • docker system prune -a

Digite y quando solicitado na saída para confirmar que você gostaria de remover o container e as imagens parados. Esteja ciente de que isso também removerá seu cache de compilação.

Agora você removeu o container que está executando a imagem da sua aplicação e a própria imagem. Para obter mais informações sobre como remover containers, imagens e volumes do Docker, consulte How To Remove Docker Images, Containers, and Volumes.

Com todas as suas imagens e containers excluídos, agora você pode baixar a imagem da aplicação do Docker Hub:

  • docker pull seu_usuário_dockerhub/nodejs-image-demo

Liste suas imagens mais uma vez:

  • docker images

Você verá a imagem da sua aplicação:

Output
REPOSITORY TAG IMAGE ID CREATED SIZE seu_usuário_dockerhub/nodejs-image-demo latest 1c723fb2ef12 11 minutes ago 895MB

Agora você pode reconstruir seu container usando o comando do Passo 3:

  • docker run --name nodejs-image-demo -p 80:8080 -d seu_usuário_dockerhub/nodejs-image-demo

Liste seus containers em execução:

docker ps 
Output
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES f6bc2f50dff6 seu_usuário_dockerhub/nodejs-image-demo "node app.js" 4 seconds ago Up 3 seconds 0.0.0.0:80->8080/tcp nodejs-image-demo

Visite http://ip_do_seu_servidor mais uma vez para ver a sua aplicação em execução.

Conclusão

Neste tutorial, você criou uma aplicação web estática com Express e Bootstrap, bem como uma imagem do Docker para esta aplicação. Você utilizou essa imagem para criar um container e enviou a imagem para o Docker Hub. A partir daí, você conseguiu destruir sua imagem e seu container e recriá-los usando seu repositório do Docker Hub.

Se você estiver interessado em aprender mais sobre como trabalhar com ferramentas como o Docker Compose e o Docker Machine para criar configurações de vários containers, consulte os seguintes guias:

Para dicas gerais sobre como trabalhar com dados de container, consulte:

Se você estiver interessado em outros tópicos relacionados ao Docker, consulte nossa biblioteca completa de tutoriais do Docker.

Por Kathleen Juell

DigitalOcean Community Tutorials