Your First Dockerfile from Scratch — Step by Step

Docker & Containers · By · Published on · Updated on · 9 min read

Hands typing on a laptop with code visible in the editor
Illustrates the practical flow: edit, build, and test your Dockerfile on a laptop Photo: Lukas Blazek / Pexels

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 guide

See also