Introdução
Neste laboratório, vamos nos aprofundar nas técnicas de Dockerfile, explorando conceitos avançados que ajudarão você a criar imagens Docker mais eficientes e flexíveis. Abordaremos instruções detalhadas de Dockerfile, builds de múltiplos estágios (multi-stage builds) e o uso de arquivos .dockerignore. Também exploraremos o conceito crucial de camadas (layers) em imagens Docker. Ao final deste laboratório, você terá uma compreensão abrangente dessas técnicas avançadas e será capaz de aplicá-las em seus próprios projetos.
Este laboratório foi projetado pensando em iniciantes, fornecendo explicações detalhadas e abordando possíveis pontos de confusão. Usaremos o WebIDE (VS Code) para todas as nossas tarefas de edição de arquivos, facilitando a criação e modificação de arquivos diretamente no navegador.
Entendendo Instruções e Camadas do Dockerfile
Vamos começar criando um Dockerfile que utiliza várias instruções. Construiremos uma imagem para uma aplicação web Python usando Flask e, ao longo do caminho, exploraremos como cada instrução contribui para as camadas da nossa imagem Docker.
- Primeiro, vamos criar um novo diretório para o nosso projeto. No terminal do WebIDE, execute:
mkdir -p ~/project/advanced-dockerfile && cd ~/project/advanced-dockerfile
Este comando cria um novo diretório chamado advanced-dockerfile dentro da pasta project e, em seguida, entra nesse diretório.
Agora, vamos criar o arquivo da nossa aplicação. No explorador de arquivos do WebIDE (geralmente no lado esquerdo da tela), clique com o botão direito na pasta
advanced-dockerfilee selecione "New File". Nomeie este arquivo comoapp.py.Abra o
app.pye adicione o seguinte código Python:
from flask import Flask
import os
app = Flask(__name__)
@app.route('/')
def hello():
return f"Hello from {os.environ.get('ENVIRONMENT', 'unknown')} environment!"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
Esta é uma aplicação Flask simples que responde com uma mensagem de saudação, incluindo o ambiente em que está sendo executada.
- Em seguida, precisamos criar um arquivo
requirements.txtpara especificar nossas dependências Python. Crie um novo arquivo chamadorequirements.txtno mesmo diretório e adicione o seguinte conteúdo:
Flask==2.0.1
Werkzeug==2.0.1
Aqui, estamos especificando versões exatas para o Flask e o Werkzeug para garantir a compatibilidade.
- Agora, vamos criar o nosso Dockerfile. Crie um novo arquivo chamado
Dockerfile(com 'D' maiúsculo) no mesmo diretório e adicione o seguinte conteúdo:
## Use an official Python runtime as the base image
FROM python:3.9-slim
## Set the working directory in the container
WORKDIR /app
## Set an environment variable
ENV ENVIRONMENT=production
## Copy the requirements file into the container
COPY requirements.txt .
## Install the required packages
RUN pip install --no-cache-dir -r requirements.txt
## Copy the application code into the container
COPY app.py .
## Specify the command to run when the container starts
CMD ["python", "app.py"]
## Expose the port the app runs on
EXPOSE 5000
## Add labels for metadata
LABEL maintainer="Your Name <your.email@example.com>"
LABEL version="1.0"
LABEL description="Flask app demo for advanced Dockerfile techniques"
Agora, vamos detalhar essas instruções e entender como elas contribuem para as camadas da nossa imagem Docker:
FROM python:3.9-slim: Esta é sempre a primeira instrução. Ela especifica a imagem base a partir da qual estamos construindo. Isso cria a primeira camada da nossa imagem, que inclui o ambiente de execução Python.WORKDIR /app: Define o diretório de trabalho para as instruções subsequentes. Não cria uma nova camada, mas afeta o comportamento das instruções seguintes.ENV ENVIRONMENT=production: Define uma variável de ambiente. Variáveis de ambiente não criam novas camadas, mas são armazenadas nos metadados da imagem.COPY requirements.txt .: Copia o arquivo de requisitos do nosso host para dentro da imagem. Isso cria uma nova camada contendo apenas este arquivo.RUN pip install --no-cache-dir -r requirements.txt: Executa um comando no contêiner durante o processo de build. Ele instala nossas dependências Python. Isso cria uma nova camada que contém todos os pacotes instalados.COPY app.py .: Copia o código da nossa aplicação para a imagem, criando outra camada.CMD ["python", "app.py"]: Especifica o comando a ser executado quando o contêiner for iniciado. Não cria uma camada, mas define o comando padrão para o contêiner.EXPOSE 5000: Na verdade, é apenas uma forma de documentação. Informa ao Docker que o contêiner ouvirá nesta porta em tempo de execução, mas não publica a porta de fato. Não cria uma camada.LABEL ...: Adiciona metadados à imagem. Assim como as instruções ENV, não criam novas camadas, mas são armazenadas nos metadados da imagem.
Cada instrução RUN, COPY e ADD em um Dockerfile cria uma nova camada. As camadas são um conceito fundamental no Docker que permite o armazenamento e a transferência eficiente de imagens. Quando você faz alterações no seu Dockerfile e reconstrói a imagem, o Docker reutiliza as camadas em cache que não foram alteradas, acelerando o processo de build.
- Agora que entendemos o que nosso Dockerfile está fazendo, vamos construir a imagem Docker. No terminal, execute:
docker build -t advanced-flask-app .
Este comando constrói uma nova imagem Docker com a tag advanced-flask-app. O . no final diz ao Docker para procurar o Dockerfile no diretório atual.
Você verá uma saída mostrando cada etapa do processo de build. Observe como cada etapa corresponde a uma instrução em nosso Dockerfile e como o Docker menciona "Using cache" para etapas que não mudaram se você executar o comando de build várias vezes.
- Assim que o build for concluído, podemos executar um contêiner baseado em nossa nova imagem:
docker run -d -p 5000:5000 --name flask-container advanced-flask-app
Este comando faz o seguinte:
-dexecuta o contêiner em modo desanexado (em segundo plano)-p 5000:5000mapeia a porta 5000 do seu host para a porta 5000 no contêiner--name flask-containerdá um nome ao nosso novo contêineradvanced-flask-appé a imagem que estamos usando para criar o contêiner
Você pode verificar se o contêiner está rodando verificando a lista de contêineres ativos:
docker ps
- Para testar se nossa aplicação está funcionando corretamente, podemos usar o comando
curl:
curl http://localhost:5000
Você deve ver a mensagem "Hello from production environment!"
Se tiver problemas com o curl, você também pode abrir uma nova aba no navegador e visitar http://localhost:5000. Você deverá ver a mesma mensagem.
Se encontrar algum problema, você pode verificar os logs do contêiner usando:
docker logs flask-container
Isso mostrará quaisquer mensagens de erro ou saídas da sua aplicação Flask.
Builds de Múltiplos Estágios
Agora que entendemos as instruções básicas e as camadas do Dockerfile, vamos explorar uma técnica mais avançada: builds de múltiplos estágios (multi-stage builds). Eles permitem que você use várias instruções FROM em seu Dockerfile. Isso é particularmente útil para criar imagens finais menores, copiando apenas os artefatos necessários de um estágio para outro.
Vamos modificar nosso Dockerfile para usar um build de múltiplos estágios que resulte em uma imagem menor:
- No WebIDE, abra o
Dockerfileque criamos anteriormente. - Substitua todo o conteúdo pelo seguinte:
## Build stage
FROM python:3.9-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
## Final stage
FROM python:3.9-slim
WORKDIR /app
## Copy only the installed packages from the builder stage
COPY --from=builder /root/.local /root/.local
COPY app.py .
ENV PATH=/root/.local/bin:$PATH
ENV ENVIRONMENT=production
CMD ["python", "app.py"]
EXPOSE 5000
LABEL maintainer="Your Name <your.email@example.com>"
LABEL version="1.0"
LABEL description="Flask app demo with multi-stage build"
Vamos analisar o que está acontecendo neste Dockerfile de múltiplos estágios:
Começamos com um estágio
builder:- Usamos a imagem Python 3.9-slim como base para manter as coisas pequenas desde o início.
- Instalamos nossas dependências Python neste estágio usando
pip install --user. Isso instala os pacotes no diretório home do usuário.
Depois, temos o nosso estágio final:
- Começamos do zero com outra imagem Python 3.9-slim.
- Copiamos apenas os pacotes instalados do estágio
builder, especificamente de/root/.local, onde opip install --useros colocou. - Copiamos o código da nossa aplicação.
- Adicionamos o diretório bin local ao PATH para que o Python possa encontrar os pacotes instalados.
- Configuramos o restante do nosso contêiner (ENV, CMD, EXPOSE, LABEL) como antes.
A principal vantagem aqui é que nossa imagem final não inclui nenhuma das ferramentas de build ou caches do processo de instalação do pip. Ela contém apenas os artefatos finais necessários. Isso deve resultar em uma imagem menor.
- Vamos construir esta nova imagem de múltiplos estágios. No terminal, execute:
docker build -t multi-stage-flask-app .
- Assim que o build for concluído, vamos comparar os tamanhos das nossas duas imagens. Execute:
docker images | grep flask-app
multi-stage-flask-app latest 7bdd1be2d1fb 10 seconds ago 129MB
advanced-flask-app latest c59d6fa303cc 10 minutes ago 136MB
Você deve notar que a multi-stage-flask-app é menor que a advanced-flask-app que construímos anteriormente.
- Agora, vamos rodar um contêiner com nossa nova imagem mais enxuta:
docker run -d -p 5001:5000 --name multi-stage-container multi-stage-flask-app
Note que estamos usando uma porta de host diferente (5001) para evitar conflitos com nosso contêiner anterior.
- Teste a aplicação:
curl http://localhost:5001
Você ainda deve ver a mensagem "Hello from production environment!"
- Para entender melhor as diferenças entre nossas imagens de estágio único e múltiplos estágios, podemos usar o comando
docker history. Execute estes comandos:
docker history advanced-flask-app
docker history multi-stage-flask-app
Compare as saídas. Você deve notar que o build de múltiplos estágios tem menos camadas e tamanhos menores para algumas delas.
Builds de múltiplos estágios são uma técnica poderosa para criar imagens Docker eficientes. Eles permitem que você use ferramentas e arquivos em seu processo de build sem inflar sua imagem final. Isso é particularmente útil para linguagens compiladas ou aplicações com processos de build complexos.
Neste caso, usamos para criar uma imagem de aplicação Python menor, copiando apenas os pacotes instalados e o código da aplicação necessários, deixando para trás quaisquer artefatos de build ou caches.
Usando o Arquivo .dockerignore
Ao construir uma imagem Docker, o Docker envia todos os arquivos do diretório para o daemon do Docker. Se você tiver arquivos grandes que não são necessários para a construção da sua imagem, isso pode tornar o processo de build mais lento. O arquivo .dockerignore permite especificar arquivos e diretórios que devem ser excluídos ao construir uma imagem Docker.
Vamos criar um arquivo .dockerignore e ver como ele funciona:
- No WebIDE, crie um novo arquivo no diretório
advanced-dockerfilee nomeie-o como.dockerignore. - Adicione o seguinte conteúdo ao arquivo
.dockerignore:
**/.git
**/.gitignore
**/__pycache__
**/*.pyc
**/*.pyo
**/*.pyd
**/.Python
**/env
**/venv
**/ENV
**/env.bak
**/venv.bak
Vamos detalhar o que esses padrões significam:
**/.git: Ignora o diretório .git e todo o seu conteúdo, onde quer que ele apareça na estrutura de diretórios.**/.gitignore: Ignora arquivos .gitignore.**/__pycache__: Ignora os diretórios de cache do Python.**/*.pyc,**/*.pyo,**/*.pyd: Ignora arquivos Python compilados.**/.Python: Ignora arquivos .Python (frequentemente criados por ambientes virtuais).**/env,**/venv,**/ENV: Ignora diretórios de ambiente virtual.**/env.bak,**/venv.bak: Ignora cópias de backup de diretórios de ambiente virtual.
O ** no início de cada linha significa "em qualquer diretório".
- Para demonstrar o efeito do arquivo
.dockerignore, vamos criar alguns arquivos que queremos ignorar. No terminal, execute:
mkdir venv
touch venv/ignore_me.txt
touch .gitignore
Esses comandos criam um diretório venv com um arquivo dentro e um arquivo .gitignore. Esses são elementos comuns em projetos Python que normalmente não queremos em nossas imagens Docker.
- Agora, vamos construir nossa imagem novamente:
docker build -t ignored-flask-app .
- Para verificar se os arquivos ignorados não foram incluídos no contexto de build, podemos usar o comando
docker history:
docker history ignored-flask-app
Você não deve ver nenhuma etapa que copie o diretório venv ou o arquivo .gitignore.
O arquivo .dockerignore é uma ferramenta poderosa para manter suas imagens Docker limpas e seu processo de build eficiente. É especialmente útil para projetos maiores, onde você pode ter muitos arquivos que não são necessários na imagem final.
Instruções Avançadas de Dockerfile
Nesta etapa final, exploraremos algumas instruções adicionais de Dockerfile e boas práticas que podem ajudar a tornar suas imagens Docker mais seguras, fáceis de manter e de usar. Também focaremos na solução de problemas e na verificação de cada etapa do processo.
No WebIDE, abra o
Dockerfilenovamente.Substitua o conteúdo pelo seguinte:
## Build stage
FROM python:3.9-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
## Final stage
FROM python:3.9-slim
## Create a non-root user
RUN useradd -m appuser
## Install curl for healthcheck
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
WORKDIR /app
## Dynamically determine Python version and site-packages path
RUN PYTHON_VERSION=$(python -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') && \
SITE_PACKAGES_PATH="/home/appuser/.local/lib/python${PYTHON_VERSION}/site-packages" && \
mkdir -p "${SITE_PACKAGES_PATH}" && \
chown -R appuser:appuser /home/appuser/.local
## Copy site-packages and binaries using the variable
COPY --from=builder /root/.local/lib/python3.9/site-packages "${SITE_PACKAGES_PATH}"
COPY --from=builder /root/.local/bin /home/appuser/.local/bin
COPY app.py .
ENV PATH=/home/appuser/.local/bin:$PATH
ENV ENVIRONMENT=production
## Set the user to run the application
USER appuser
## Use ENTRYPOINT with CMD
ENTRYPOINT ["python"]
CMD ["app.py"]
EXPOSE 5000
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:5000/ || exit 1
ARG BUILD_VERSION
LABEL maintainer="Your Name <your.email@example.com>"
LABEL version="${BUILD_VERSION:-1.0}"
LABEL description="Flask app demo with advanced Dockerfile techniques"
Vamos analisar os novos conceitos introduzidos neste Dockerfile:
RUN useradd -m appuser: Cria um novo usuário chamadoappuserno contêiner. Executar aplicações como um usuário não-root é uma boa prática de segurança, pois limita os danos potenciais se a aplicação for comprometida. A flag-mcria um diretório home para o usuário.RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*: Instala o pacote curl, necessário para que nossa instrução HEALTHCHECK funcione. Também limpamos o cache do apt para reduzir o tamanho da imagem.RUN PYTHON_VERSION=$(python -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') && ...: Este conjunto de comandos determina dinamicamente a versão do Python dentro do contêiner e cria o diretóriosite-packagescorreto para oappuser. Também define as permissões corretas para o diretório local do usuário.COPY --from=builder /root/.local/lib/python3.9/site-packages "${SITE_PACKAGES_PATH}": Esta instrução copia os pacotes Python instalados do estágiobuilderpara o caminhosite-packagesdeterminado dinamicamente na imagem final, garantindo que os pacotes sejam colocados no local correto para oappuserusar.COPY --from=builder /root/.local/bin /home/appuser/.local/bin: Copia os scripts executáveis instalados pelopip(como a interface de linha de comando do Flask, se houver) do estágiobuilderpara o diretóriobinlocal doappuser.ENTRYPOINT ["python"]comCMD ["app.py"]: Quando usados juntos, oENTRYPOINTdefine o executável principal do contêiner (neste caso,python) e oCMDfornece os argumentos padrão para esse executável (app.py). Esse padrão permite flexibilidade: os usuários podem rodar o contêiner e executar oapp.pypor padrão, ou podem sobrescrever oCMDpara rodar outros scripts ou comandos Python.HEALTHCHECK: Esta instrução configura uma verificação de saúde para o contêiner. O Docker executará periodicamente o comando especificado (curl -f http://localhost:5000/) para determinar se o contêiner está saudável. As flags--interval=30se--timeout=3sdefinem o intervalo de verificação e o tempo limite, respectivamente. Se o comandocurlfalhar (retornar um código de saída diferente de zero), o contêiner será considerado não saudável (unhealthy).ARG BUILD_VERSION: Define um argumento de build chamadoBUILD_VERSION. Argumentos de build permitem passar valores para a imagem Docker durante o tempo de construção.LABEL version="${BUILD_VERSION:-1.0}": Define um rótulo (label) chamadoversionna imagem Docker. Ele usa o argumento de buildBUILD_VERSION. SeBUILD_VERSIONfor fornecido durante o build, seu valor será usado; caso contrário, o padrão será1.0(usando a sintaxe de valor padrão:-).
- Agora, vamos construir esta nova imagem, especificando uma versão de build:
docker build -t advanced-flask-app-v2 --build-arg BUILD_VERSION=2.0 .
A flag --build-arg BUILD_VERSION=2.0 nos permite passar o valor 2.0 para o argumento de build BUILD_VERSION durante o processo de construção da imagem. Esse valor será usado para definir o label version na imagem Docker.
- Assim que o build for concluído, vamos verificar se a imagem foi criada com sucesso:
docker images | grep advanced-flask-app-v2
Você deve ver a nova imagem advanced-flask-app-v2 listada na saída do comando docker images, junto com sua tag, ID da imagem, data de criação e tamanho.
- Agora, vamos rodar um contêiner com a nova imagem:
docker run -d -p 5002:5000 --name advanced-container-v2 advanced-flask-app-v2
Este comando executa um contêiner em modo desanexado (-d), mapeia a porta 5002 do seu host para a porta 5000 no contêiner (-p 5002:5000), nomeia o contêiner como advanced-container-v2 (--name advanced-container-v2) e usa a imagem advanced-flask-app-v2 para criar o contêiner.
- Vamos verificar se o contêiner está rodando:
docker ps | grep advanced-container-v2
Se o contêiner estiver rodando com sucesso, você deverá vê-lo listado na saída do comando docker ps. Se não vir o contêiner listado, ele pode ter encerrado. Vamos verificar se há contêineres parados:
docker ps -a | grep advanced-container-v2
Se você vir o contêiner listado na saída de docker ps -a, mas ele não estiver rodando (o status não é "Up"), podemos verificar seus logs em busca de erros:
docker logs advanced-container-v2
Este comando exibirá os logs do contêiner advanced-container-v2, o que pode ajudar a diagnosticar quaisquer problemas de inicialização ou erros de tempo de execução em sua aplicação Flask.
- Assumindo que o contêiner está rodando, após dar um momento para ele iniciar, podemos verificar seu status de saúde:
docker inspect --format='{{.State.Health.Status}}' advanced-container-v2
Após um curto atraso (para permitir que a verificação de saúde seja executada pelo menos uma vez), você deve ver "healthy" como saída. Se vir "unhealthy" inicialmente, aguarde mais 30 segundos (o intervalo da verificação de saúde) e execute o comando novamente. Se permanecer "unhealthy", verifique os logs do contêiner usando docker logs advanced-container-v2 para identificar possíveis problemas com sua aplicação Flask. Se não houver problemas óbvios, você pode ignorar o status "unhealthy".
- Também podemos verificar se o nosso label de versão de build foi aplicado corretamente:
docker inspect -f '{{.Config.Labels.version}}' advanced-container-v2
Este comando recupera o valor do label version do contêiner advanced-container-v2 e o exibe. Você deve ver "2.0" como saída, o que confirma que o argumento de build BUILD_VERSION foi usado corretamente para definir o label.
- Finalmente, vamos testar nossa aplicação enviando uma requisição para ela:
curl http://localhost:5002
Você deve ver a mensagem "Hello from production environment!" na saída. Isso indica que sua aplicação Flask está rodando corretamente dentro do contêiner Docker e está acessível na porta 5002 do seu host.
Essas técnicas avançadas permitem criar imagens Docker mais seguras, configuráveis e prontas para produção. O usuário não-root melhora a segurança, o HEALTHCHECK ajuda na orquestração e monitoramento de contêineres, e os argumentos de build permitem uma construção de imagem mais flexível e versionada.
Resumo
Neste laboratório, exploramos técnicas avançadas de Dockerfile que ajudarão você a criar imagens Docker mais eficientes, seguras e fáceis de manter. Cobrimos:
- Instruções detalhadas de Dockerfile e seu impacto nas camadas da imagem: Aprendemos como cada instrução contribui para a estrutura da nossa imagem Docker e como entender as camadas pode nos ajudar a otimizar nossas imagens.
- Builds de múltiplos estágios: Usamos essa técnica para criar imagens finais menores, separando nosso ambiente de build do nosso ambiente de execução.
- Uso de arquivos .dockerignore: Aprendemos como excluir arquivos desnecessários do nosso contexto de build, o que pode acelerar as construções e reduzir o tamanho da imagem.
- Instruções avançadas de Dockerfile: Exploramos instruções adicionais como USER, ENTRYPOINT, HEALTHCHECK e ARG, que nos permitem criar imagens mais seguras e flexíveis.
Essas técnicas permitem que você:
- Crie imagens Docker mais otimizadas e menores
- Melhore a segurança executando aplicações como usuários não-root
- Implemente verificações de saúde para uma melhor orquestração de contêineres
- Use variáveis em tempo de build para uma construção de imagem mais flexível
Ao longo deste laboratório, usamos o WebIDE (VS Code) para editar nossos arquivos, facilitando a criação e modificação de Dockerfiles e do código da aplicação diretamente no navegador. Essa abordagem permite uma experiência de desenvolvimento contínua ao trabalhar com Docker.



