Your First Dockerfile from Scratch — Step by Step
Docker & Containers · By Kevin Marcondes · Published on · Updated on · 9 min read
Aprenda how to create your first Dockerfile com um exemplo simples e validado no final. Você vai criar um app mínimo, escrever o Dockerfile, construir a imagem, rodar o container, entender o cache de camadas, usar .dockerignore e um usuário não‑root. O guia também explica quando usar CMD/ENTRYPOINT, BuildKit e como publicar a imagem.
Prerequisites and a quick overview of what you'll do
Você vai precisar:
Vamos criar um app simples, escrever o Dockerfile, construir a imagem, rodar o container e otimizar com .dockerignore e usuário não‑root. Se ainda não tem o Docker, veja o guia de instalação para Linux e Compose antes de continuar.
Em ambientes Linux, confirme que seu usuário consegue usar o Docker sem sudo. Opcionalmente, habilite o BuildKit para builds mais rápidos e cache avançado.
- Docker instalado e em execução. Em Linux, adicione seu usuário ao grupo docker.
- Um editor de texto e um terminal.
- Algum espaço em disco para camadas de imagens.
- Dica: veja como instalar e atualizar Docker e Compose em Linux em instalar Docker e Docker Compose.
# Verifique se o Docker está funcionando
docker --version
# (Opcional) Teste rápido do daemon
docker run --rm hello-world
Basic structure of a Dockerfile
Um Dockerfile descreve como montar uma imagem a partir de camadas. Cada instrução cria ou afeta uma camada em cache. Abaixo, veja diretivas comuns e um exemplo mínimo. Prefira bases leves (ex.: variantes slim) para reduzir tamanho.
Dicas úteis: use a forma exec (arrays JSON) em CMD/ENTRYPOINT para evitar shell implícito; defina variáveis com ENV e passe parâmetros de build com ARG; ordene as instruções para maximizar o reaproveitamento de cache.
Sobre "from scratch": é uma base vazia, usada com binários estáticos (ex.: Go) em multi‑stage. Para apps interpretados (Python/Node), use uma base mínima (alpine/slim) e elimine dependências de build na imagem final.
| Directive | Purpose | Example |
|---|---|---|
| FROM | Base image to start from | FROM python:3.12-slim |
| WORKDIR | Sets the working directory | WORKDIR /app |
| COPY | Copies files into the image | COPY . . |
| RUN | Executes build-time commands | RUN pip install -r requirements.txt |
| EXPOSE | Documents the port the app uses | EXPOSE 8000 |
| CMD | Default command on container start | CMD ["python", "app.py"] |
# Exemplo mínimo
FROM alpine:3.20
CMD ["echo", "Hello from Docker!"]
Prepare the example project (Flask)
Vamos criar uma API Flask mínima para validar rede e logs. No Windows, se preferir, crie os arquivos manualmente em vez de usar heredoc. O projeto terá requirements.txt e app.py. Em Windows, garanta que o fim de linha seja LF para evitar problemas com imagens baseadas em Linux.
mkdir hello-docker && cd hello-docker
# requirements.txt
cat > requirements.txt <<'REQ'
flask
REQ
# app.py
cat > app.py <<'PY'
from flask import Flask
app = Flask(__name__)
@app.get("/")
def index():
return {"message": "Hello from Docker"}
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)
PY
Write the Dockerfile step by step
Crie um arquivo chamado Dockerfile na raiz do projeto. Primeiro copie as dependências para aproveitar o cache; depois copie o código da aplicação. Exponha a porta e defina o comando padrão. Para imagens mais leves, fixe versões de dependências e da base.
Para stacks diferentes, ajuste FROM, portas e o comando final. Para reduzir ainda mais o tamanho, considere multi‑stage builds — veja o guia de imagens leves em como criar imagens Docker leves.
# Dockerfile (versão inicial)
FROM python:3.12-slim
# Impede criação de .pyc e ativa logs imediatos no stdout/stderr
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
# Define diretório de trabalho
WORKDIR /app
# Copia dependências primeiro para aproveitar cache
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
# Copia o restante do código
COPY . .
# Documenta a porta usada
EXPOSE 8000
# Comando padrão
CMD ["python", "app.py"]
Build the image and understand layer caching
O build executa as instruções do Dockerfile e monta a imagem. Alterar arquivos copiados antes invalida o cache dos passos seguintes. Se você muda apenas o código e mantém o requirements.txt, a camada de dependências é reaproveitada.
Ative o BuildKit para builds mais rápidos e recursos como cache de compilação. Em CI/CD, automatize login, build, tag e push; use cache remoto quando fizer sentido. Para detalhes, veja BuildKit e cache remoto e multi‑arquitetura em imagens multi‑arch.
# Execute a partir da pasta do projeto (onde está o Dockerfile)
DOCKER_BUILDKIT=1 docker build -t hello-docker:dev .
# Verifique a imagem criada
docker image ls | grep hello-docker
# Rode novamente para observar o uso de cache
DOCKER_BUILDKIT=1 docker build -t hello-docker:dev .
# (Opcional) Inspecione o histórico de camadas
docker image history hello-docker:dev
# (Opcional) Publique em um registry
# docker login registry.example.com
# docker tag hello-docker:dev registry.example.com/seu-usuario/hello-docker:dev
# docker push registry.example.com/seu-usuario/hello-docker:dev
Run the container and map the port
Agora rode o container e mapeie a porta 8000 do host para a 8000 do container. Use -d para rodar em background. Para parar, use docker stop. O mapeamento de portas permite acessar a aplicação no navegador.
Para configurar variáveis, use -e ou --env-file. Para persistir dados, mapeie volumes com -v. Se for publicar imagens privadas, veja como criar um registry Docker privado.
# Executa em foreground (Ctrl+C para parar)
docker run --rm -p 8000:8000 --name hello hello-docker:dev
# Ou em background (daemon)
# docker run -d --rm -p 8000:8000 --name hello hello-docker:dev
# Em outro terminal, teste:
# curl http://localhost:8000
Optimize: .dockerignore and running as a non-root user
Reduza o contexto de build e melhore a segurança. Um .dockerignore evita enviar arquivos desnecessários ao daemon. Rodar o processo como não‑root diminui o impacto de falhas. Se sua imagem expõe uma API pública, adote boas práticas de segurança e minimize a superfície de ataque.
Use multi‑stage para separar dependências de build do runtime e, quando aplicável (binário estático), considerar uma imagem final FROM scratch. Para apps interpretados, prefira bases slim e instale apenas o necessário. Para entender multi‑stage em profundidade, veja imagens Docker leves.
# .dockerignore (crie este arquivo na raiz)
__pycache__/
*.pyc
.env
.git
.gitignore
README*
# Dockerfile (otimizado)
FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
# Camada de dependências (cacheável)
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
# Copia o código
COPY . .
# Cria usuário não-root e ajusta permissões
RUN adduser --disabled-password --gecos "" appuser \
&& chown -R appuser:appuser /app
USER appuser
EXPOSE 8000
CMD ["python", "app.py"]
How to confirm it worked and troubleshoot
Verifique a aplicação, os logs e a saúde do container. Se algo falhar, inspecione mensagens de erro e o mapeamento de portas. Padronize logs estruturados quando possível. Para desligamento limpo, garanta que seu processo lide com SIGTERM.
Problemas comuns e soluções:
- Não abre no navegador: confirme -p 8000:8000 e acesse http://localhost:8000.
- Porta em uso: mapeie outra porta do host, por exemplo -p 8080:8000.
- Permission denied ao usar USER: garanta chown -R appuser:appuser /app no Dockerfile.
- Dependência faltando: atualize requirements.txt e faça rebuild sem cache (use --no-cache).
- Daemon inacessível: inicie o Docker/Desktop e verifique permissões do usuário (em Linux, grupo docker).
- Imagem muito grande: use base slim, .dockerignore e limpe caches quando possível; considere multi‑stage.
- Erros intermitentes: adicione HEALTHCHECK no Dockerfile para monitorar; verifique readiness antes de expor tráfego.
- Falha por fim de linha: converta arquivos para LF ao trabalhar em Windows.
# Teste HTTP e logs
curl -s http://localhost:8000 | jq .
docker ps --filter name=hello
docker logs -f hello
# (Opcional) Acesse o shell do container para inspeção
# docker exec -it hello sh
Frequently asked questions
What is a Dockerfile and why do I need one?
Um Dockerfile é um arquivo de texto com instruções para construir uma imagem de container. Ele garante builds reprodutíveis, padroniza o ambiente e facilita a distribuição da sua aplicação, evitando setups manuais e sujeitos a erros.
What's the difference between CMD and ENTRYPOINT?
CMD define o comando padrão e pode ser sobrescrito em runtime. ENTRYPOINT define o executável principal; argumentos adicionais podem vir do CMD. Use ENTRYPOINT quando o processo principal não deve mudar; use CMD para defaults fáceis de substituir.
Is .dockerignore mandatory?
Não é obrigatório, mas é altamente recomendado. Ele reduz o contexto de build, acelera builds e evita que arquivos sensíveis sejam enviados para o daemon. Exclua diretórios de build, caches, VCS e segredos sempre que possível.
How do I reduce build time and leverage cache?
Copie e instale dependências antes do código, pois mudam com menos frequência. Minimize RUNs desnecessários, combine comandos quando fizer sentido, mantenha a base enxuta e ative o BuildKit. Em CI, reutilize camadas e cache remoto quando aplicável.
How do I publish the image to a registry?
Escolha um registry, faça login com a CLI, aplique uma tag com o namespace do registry e faça push. Em pipelines CI/CD, automatize login, build, tagging e push, validando também políticas de acesso e nomes consistentes por ambiente.
What does FROM scratch mean in Docker?
FROM scratch cria a imagem a partir de uma base vazia, sem utilitários. É ideal para binários estáticos (por exemplo, Go) produzidos em multi‑stage: você compila em uma imagem de build e copia apenas o binário final para a imagem FROM scratch, reduzindo a superfície de ataque e o tamanho.
Ready for the next step?
Automatize builds e publicações no seu pipeline. Use GitHub Actions para construir, testar e publicar imagens a cada push.
Read the CI/CD guideSee also
- Tutorial: Implement Canary releases with Traefik in Docker Compose using headers and weighted routing for gradual PHP deployments
- Tutorial: Configure HAProxy with Let's Encrypt and health checks to load balance traffic between Docker containers on a VPS (step-by-step)
- Hands‑On Tutorial: Implement mTLS Between Nginx and Microservices with smallstep CA in Docker Compose
- Tutorial: configure cgroup v2 and systemd ResourceControl to limit CPU, memory and I/O for Docker containers on Debian