Docker empacota aplicações com suas dependências em containers isolados. Cada container usa o kernel do host mas tem seu próprio filesystem, processos e rede. Isso garante que “funciona na minha máquina” seja verdade em qualquer ambiente.
Conceitos Fundamentais
Image: template somente leitura criado a partir de um Dockerfile. Composta por camadas (layers) empilhadas. Cada instrução no Dockerfile cria uma nova layer.
Container: instância em execução de uma image. Tem estado mutável (layer de escrita) mas a image base é imutável.
Layer: cada instrução RUN, COPY, ADD no Dockerfile cria uma layer. Layers são cacheadas — se uma layer não mudou, o Docker reutiliza o cache.
Registry: repositório de images. Docker Hub é o padrão público. Harbor, ECR, GCR são registries privados.
Volume: mecanismo para persistir dados fora do ciclo de vida do container. O container pode ser destruído e recriado sem perder os dados do volume.
Network: rede virtual que conecta containers. Containers na mesma rede se comunicam pelo nome do container (DNS interno).
Layer stack de uma image:
┌─────────────────────────────┐
│ Layer 4: COPY app.jar │ ← muda em cada build (código)
├─────────────────────────────┤
│ Layer 3: RUN mvn package │ ← muda quando dependências mudam
├─────────────────────────────┤
│ Layer 2: COPY pom.xml │ ← muda quando pom.xml muda
├─────────────────────────────┤
│ Layer 1: FROM eclipse-temurin:21│ ← raramente muda (base image)
└─────────────────────────────┘
Cache hit: se a layer não mudou, Docker reutiliza do cacheDockerfile Básico
Instruções mais usadas com suas diferenças:
# FROM — define a base image
# Use tags específicas, nunca :latest em produção
FROM eclipse-temurin:21-jre-alpine
# WORKDIR — define o diretório de trabalho dentro do container
# Cria o diretório se não existir; evita usar caminhos relativos
WORKDIR /app
# COPY — copia arquivos do host para o container
# Preferido em relação a ADD para arquivos locais
COPY target/app.jar app.jar
# ADD — como COPY mas também extrai .tar e suporta URLs remotas
# Use ADD apenas quando precisar dessas funcionalidades extras
ADD https://example.com/config.tar.gz /config/
# RUN — executa comando durante o build (cria nova layer)
# Combine em uma linha com && para minimizar layers
RUN apt-get update && \
apt-get install -y curl && \
rm -rf /var/lib/apt/lists/*
# ENV — define variável de ambiente disponível em runtime
ENV APP_PORT=8080 \
JAVA_OPTS="-Xmx512m"
# EXPOSE — documenta a porta que o container escuta (não publica de fato)
EXPOSE 8080
# CMD vs ENTRYPOINT — diferença importante:
# CMD — comando padrão, pode ser sobrescrito no docker run
CMD ["java", "-jar", "app.jar"]
# ENTRYPOINT — comando fixo, args do docker run são passados como parâmetros
ENTRYPOINT ["java", "-jar", "app.jar"]
# docker run myapp --spring.profiles.active=prod
# executa: java -jar app.jar --spring.profiles.active=prod
# Combinação comum: ENTRYPOINT fixo + CMD como args default
ENTRYPOINT ["java"]
CMD ["-jar", "app.jar"]
# docker run myapp -jar app.jar ← usa CMD
# docker run myapp -Xmx1g -jar app.jar ← sobrescreve CMD
# HEALTHCHECK — verifica se o container está saudável
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
# LABEL — metadados da image
LABEL maintainer="rafael@empresa.com" \
version="1.0.0" \
description="Order Service"Dockerfile Avançado — Multi-Stage Build
Multi-stage builds reduzem drasticamente o tamanho da image final separando as ferramentas de build das dependências de runtime.
# ═══ STAGE 1: Dependências (cache otimizado) ═══
FROM maven:3.9-eclipse-temurin-21 AS dependencies
WORKDIR /build
# Copia apenas pom.xml primeiro — cache desta layer é válido
# enquanto pom.xml não mudar (mesmo que o código mude)
COPY pom.xml .
RUN mvn dependency:go-offline -q
# ═══ STAGE 2: Build ═══
FROM dependencies AS build
# Agora copia o código-fonte (layer que muda frequentemente)
COPY src ./src
RUN mvn package -DskipTests -q
# ═══ STAGE 3: Runtime (image final enxuta) ═══
FROM eclipse-temurin:21-jre-alpine AS runtime
# Segurança: não rodar como root
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
# Copia apenas o JAR compilado — sem Maven, sem código-fonte, sem dependências dev
COPY --from=build /build/target/app.jar app.jar
# Altera propriedade para o usuário não-root
RUN chown appuser:appgroup app.jar
# Usa o usuário não-root
USER appuser
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD wget -qO- http://localhost:8080/actuator/health/readiness || exit 1
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]# Multi-stage para Node.js (React/Vue)
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
FROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:1.27-alpine AS runtime
# Remove config padrão do nginx
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/
# Copia apenas o build final
COPY --from=build /app/dist /usr/share/nginx/html
# Usuário não-root para nginx
RUN chown -R nginx:nginx /usr/share/nginx/html && \
chown -R nginx:nginx /var/cache/nginx && \
touch /var/run/nginx.pid && \
chown nginx:nginx /var/run/nginx.pid
USER nginx
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD wget -qO- http://localhost/health || exit 1.dockerignore
Evita copiar arquivos desnecessários para o contexto de build, acelerando o build e reduzindo o tamanho da image.
# .dockerignore
# Controle de versão
.git
.gitignore
# Dependências locais (serão instaladas no container)
node_modules
target
.mvn
# Arquivos de desenvolvimento
*.md
.env
.env.local
.env.*.local
# IDE e OS
.idea
.vscode
*.iml
.DS_Store
Thumbs.db
# Logs e temporários
*.log
tmp
.tmp
# Testes
**/__tests__
**/*.test.ts
**/*.spec.ts
coverage
# Docker
Dockerfile*
docker-compose*.ymlDockerfile Best Practices
Boas práticas para builds mais rápidos, images menores e containers mais seguros:
# ✅ 1. Ordem das instruções — do menos frequente para o mais frequente
# Aproveita o cache do Docker ao máximo
FROM eclipse-temurin:21-jre-alpine
# Estas layers raramente mudam — ficam no início
RUN apk add --no-cache curl
# Esta muda apenas quando pom.xml muda
COPY pom.xml .
RUN mvn dependency:go-offline
# Esta muda a cada build com mudança de código
COPY src ./src
RUN mvn package -DskipTests
# ✅ 2. Minimizar camadas — combine RUN commands
# ❌ Ruim: 3 layers
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*
# ✅ Bom: 1 layer
RUN apt-get update && \
apt-get install -y curl && \
rm -rf /var/lib/apt/lists/*
# ✅ 3. Usuário não-root
RUN addgroup -S app && adduser -S app -G app
USER app
# ✅ 4. Imagens base específicas (não :latest)
FROM eclipse-temurin:21.0.5_11-jre-alpine # versão exata
# FROM eclipse-temurin:latest # ❌ imprevisível
# ✅ 5. HEALTHCHECK em todo container de produção
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
# ✅ 6. LABEL para rastreabilidade
LABEL org.opencontainers.image.source="https://github.com/empresa/order-service" \
org.opencontainers.image.version="1.2.3" \
org.opencontainers.image.revision="abc1234"
# ✅ 7. Filesystem somente leitura quando possível (runtime flag, não Dockerfile)
# docker run --read-only --tmpfs /tmp myimagedocker run — Flags Importantes
# Flags básicas
docker run \
-d \ # detached (background)
-p 8080:8080 \ # porta host:container
-p 127.0.0.1:5432:5432 \ # bind apenas no localhost
-e SPRING_PROFILES_ACTIVE=prod \ # variável de ambiente
-e DB_PASSWORD="$(cat secret)" \ # valor de arquivo
--name order-service \ # nome do container
--rm \ # remove ao parar
--network ecommerce-net \ # conecta à rede
--restart unless-stopped \ # política de restart
myimage:1.0.0
# Políticas de restart
--restart no # nunca reinicia (padrão)
--restart always # sempre reinicia (inclusive na inicialização do Docker)
--restart unless-stopped # sempre reinicia, exceto se parado manualmente
--restart on-failure:3 # reinicia em caso de erro, máximo 3 vezes
# Limites de recursos
docker run \
--memory 512m \ # limite de RAM
--memory-swap 512m \ # swap igual ao memory = sem swap
--cpus 0.5 \ # 50% de 1 CPU
--cpu-shares 512 \ # peso relativo (padrão=1024)
myimage
# Volumes
docker run \
-v /host/path:/container/path \ # bind mount
-v myvolume:/container/path \ # named volume
-v /container/path \ # volume anônimo
--mount type=tmpfs,target=/tmp \ # tmpfs (apenas memória)
myimage
# Modo interativo e remoção ao sair
docker run -it --rm alpine sh
# Exec em container rodando
docker exec -it container-name sh
docker exec -it container-name bash
docker exec container-name cat /etc/hostsDocker Compose
# docker-compose.yml
version: '3.9'
services:
# ═══ API ═══
api:
build:
context: .
dockerfile: Dockerfile
target: runtime # stop em stage específico do multi-stage
args:
BUILD_VERSION: "1.0.0" # --build-arg
image: ecommerce/api:1.0.0
container_name: ecommerce-api
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=docker
- DB_HOST=postgres
- DB_PORT=5432
env_file:
- .env # variáveis de arquivo .env
depends_on:
postgres:
condition: service_healthy # aguarda healthcheck
kafka:
condition: service_started
networks:
- backend
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
deploy:
resources:
limits:
memory: 512m
cpus: '0.5'
# ═══ PostgreSQL ═══
postgres:
image: postgres:17-alpine
container_name: ecommerce-postgres
environment:
POSTGRES_DB: ecommerce
POSTGRES_USER: ${DB_USER:-dev}
POSTGRES_PASSWORD: ${DB_PASSWORD:-dev}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./sql/init:/docker-entrypoint-initdb.d # scripts de inicialização
ports:
- "127.0.0.1:5432:5432" # apenas localhost
networks:
- backend
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-dev} -d ecommerce"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
# ═══ Redis ═══
redis:
image: redis:7-alpine
container_name: ecommerce-redis
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
networks:
- backend
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
# ═══ Kafka ═══
kafka:
image: confluentinc/cp-kafka:7.7.0
container_name: ecommerce-kafka
environment:
KAFKA_NODE_ID: 1
KAFKA_PROCESS_ROLES: broker,controller
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
CLUSTER_ID: MkU3OEVBNTcwNTJENDM2Qk
volumes:
- kafka_data:/var/lib/kafka/data
networks:
- backend
profiles:
- messaging # só sobe com: docker compose --profile messaging up
volumes:
postgres_data:
driver: local
redis_data:
driver: local
kafka_data:
driver: local
networks:
backend:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16# docker-compose.override.yml — sobrescreve para desenvolvimento local
# Aplicado automaticamente quando existe no mesmo diretório
services:
api:
build:
target: development # usa stage de dev do multi-stage
volumes:
- .:/app # live reload: monta código-fonte
- /app/node_modules # exceção: não monta node_modules do host
environment:
- SPRING_DEVTOOLS_RESTART_ENABLED=true
- DEBUG=true
ports:
- "5005:5005" # porta de debug Java
# Uso:
# docker compose up ← aplica override automaticamente
# docker compose -f docker-compose.yml up ← sem overrideVolumes — Quando Usar Cada Tipo
# NAMED VOLUME — dados persistentes gerenciados pelo Docker
# Use para: bancos de dados, uploads, dados que devem sobreviver ao container
docker volume create postgres_data
docker run -v postgres_data:/var/lib/postgresql/data postgres:17
# BIND MOUNT — monta diretório do host
# Use para: código-fonte em desenvolvimento, configs, certificados
docker run -v /home/rafael/app:/app myimage
docker run -v ./nginx.conf:/etc/nginx/nginx.conf:ro nginx # :ro = somente leitura
# TMPFS — apenas em memória, não persiste
# Use para: dados temporários sensíveis, performance em /tmp
docker run --tmpfs /tmp:size=100m,mode=1777 myimage
# Ou no compose:
# tmpfs:
# - /tmp:size=100m
# Named volume vs bind mount — comparação:
# Named volume: portátil, gerenciado pelo Docker, sem problemas de permissão entre OS
# Bind mount: acesso direto ao filesystem do host, útil em desenvolvimentoNetworks — Tipos e DNS Interno
# BRIDGE (padrão) — containers na mesma rede se comunicam por nome
docker network create --driver bridge ecommerce-net
docker run --network ecommerce-net --name postgres postgres:17
docker run --network ecommerce-net --name api \
-e DB_HOST=postgres \ # usa o nome do container como hostname!
myapi
# HOST — container usa a rede do host diretamente (sem isolamento)
# Use para: performance máxima, quando precisar de multicast/broadcast
docker run --network host myimage
# NONE — sem rede
docker run --network none myimage
# OVERLAY — para Docker Swarm (múltiplos hosts)
docker network create --driver overlay --attachable swarm-net
# Inspeção de rede
docker network ls
docker network inspect ecommerce-net
# DNS interno: containers se comunicam pelo nome (apenas na mesma rede)
# postgres:5432, redis:6379, kafka:9092Docker Build — BuildKit e Cache
# BuildKit — engine de build moderno (padrão no Docker 23+)
export DOCKER_BUILDKIT=1
# Build com tag
docker build -t minha-app:1.0.0 .
docker build -t minha-app:latest -t minha-app:1.0.0 .
# Build com build args (passados via --build-arg)
docker build \
--build-arg BUILD_VERSION=1.0.0 \
--build-arg NODE_ENV=production \
-t minha-app:1.0.0 .
# Build específico de um stage (multi-stage)
docker build --target build -t minha-app:build .
docker build --target runtime -t minha-app:1.0.0 .
# Cache externo — reutiliza cache de CI/CD anterior
docker build \
--cache-from type=registry,ref=myregistry.com/myapp:cache \
--cache-to type=registry,ref=myregistry.com/myapp:cache,mode=max \
-t myapp:1.0.0 .
# Forçar rebuild sem cache
docker build --no-cache -t minha-app:1.0.0 .
# Build multiplataforma (AMD64 + ARM64)
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t minha-app:1.0.0 \
--push .Registry — Pull, Push e Login
# Login
docker login # Docker Hub (pede usuário/senha)
docker login myregistry.homelab.com # registry privado
docker login -u usuario -p "$TOKEN" registry.exemplo.com
# Pull
docker pull nginx:1.27-alpine
docker pull myregistry.com/myapp:1.0.0
# Tag — necessário antes de push para registry não-padrão
docker tag myapp:1.0.0 myregistry.com/namespace/myapp:1.0.0
docker tag myapp:1.0.0 myregistry.com/namespace/myapp:latest
# Push
docker push myregistry.com/namespace/myapp:1.0.0
docker push myregistry.com/namespace/myapp:latest
# Listar e inspecionar
docker images
docker images --filter dangling=false --filter label=maintainer=rafael
docker image inspect myapp:1.0.0
docker history myapp:1.0.0 # ver as layers e seus tamanhosComandos de Inspeção e Diagnóstico
# LOGS
docker logs container-name # todos os logs
docker logs -f container-name # follow (stream)
docker logs --since 1h container-name # última 1 hora
docker logs --since 2024-01-15T10:00:00 \
--until 2024-01-15T11:00:00 \
container-name
docker logs --tail 100 container-name # últimas 100 linhas
# INSPECT — JSON completo com todas as configs
docker inspect container-name
docker inspect --format '{{.NetworkSettings.IPAddress}}' container-name
docker inspect --format '{{.State.Status}}' container-name
docker inspect --format '{{json .Mounts}}' container-name | jq
# STATS — uso de recursos em tempo real
docker stats # todos os containers
docker stats container-name # container específico
docker stats --no-stream container-name # snapshot único (sem stream)
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"
# TOP — processos dentro do container
docker top container-name
docker top container-name aux
# EXEC — executar comando em container rodando
docker exec -it container-name sh
docker exec -it container-name bash
docker exec container-name env # listar variáveis de ambiente
docker exec container-name ps aux # processos
docker exec container-name cat /etc/hosts
# COPY — copiar arquivos para/de container
docker cp container-name:/app/logs ./logs # do container para host
docker cp ./config.yml container-name:/app/config.yml # do host para container
# DIFF — mudanças no filesystem do container
docker diff container-nameLimpeza — Liberando Espaço em Disco
# Ver uso de disco do Docker
docker system df
docker system df -v # detalhado
# Limpeza seletiva
docker image prune # remove images sem tag (dangling)
docker image prune -a # remove todas images não usadas por containers
docker container prune # remove containers parados
docker volume prune # remove volumes não usados
docker network prune # remove redes sem containers
# Limpeza total (use com cuidado!)
docker system prune # containers, networks e images dangling
docker system prune -a # + todas as images não usadas
docker system prune -af --volumes # tudo, incluindo volumes
# Remover itens específicos
docker rmi myimage:1.0.0
docker rmi $(docker images -f dangling=true -q) # todas as dangling images
docker rm $(docker ps -aq -f status=exited) # todos os containers paradosSegurança — Non-Root, Read-Only e Secrets
# ✅ Usuário não-root com UID/GID explícito
RUN groupadd --gid 1001 appgroup && \
useradd --uid 1001 --gid appgroup --no-create-home appuser
USER 1001:1001 # por UID para evitar dependência de /etc/passwd
# ✅ Filesystem somente leitura (no Dockerfile, via runtime flag)
# docker run --read-only --tmpfs /tmp --tmpfs /var/log myimage# ✅ SECRETS — não passar senhas via -e (visível em docker inspect)
# Docker Secrets (Swarm mode)
echo "minha_senha_secreta" | docker secret create db_password -
docker service create \
--secret db_password \
--env DB_PASSWORD_FILE=/run/secrets/db_password \
myservice
# Alternativa: montar arquivo com secret
docker run \
-v /run/secrets/db_password:/run/secrets/db_password:ro \
-e DB_PASSWORD_FILE=/run/secrets/db_password \
myimage
# No código, ler do arquivo:
# String password = Files.readString(Path.of(System.getenv("DB_PASSWORD_FILE")));# docker-compose.yml com secrets
secrets:
db_password:
file: ./secrets/db_password.txt
services:
api:
secrets:
- db_password
environment:
- DB_PASSWORD_FILE=/run/secrets/db_password# ✅ Security options
docker run \
--security-opt no-new-privileges:true \ # previne escalada de privilégios
--cap-drop ALL \ # remove todas as capacidades
--cap-add NET_BIND_SERVICE \ # adiciona apenas o necessário
--read-only \ # filesystem somente leitura
myimageBuildKit Avançado
Habilitando BuildKit
BuildKit é o backend moderno de build do Docker, com cache granular, builds paralelos e recursos avançados de montagem.
# Habilitar por variável de ambiente (Docker < 23)
DOCKER_BUILDKIT=1 docker build -t myapp .
# Docker 23+ e Docker Desktop: BuildKit é padrão
# Desabilitar explicitamente se necessário:
DOCKER_BUILDKIT=0 docker build .
# Usar buildx (sempre usa BuildKit)
docker buildx build -t myapp .--mount=type=cache — cache de dependências entre builds
Evita re-baixar dependências a cada build, mesmo quando o layer muda.
# Node.js — cache do npm
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci --prefer-offline
COPY . .
RUN npm run build
# Maven — cache do repositório local
FROM maven:3.9-eclipse-temurin-21
WORKDIR /app
COPY pom.xml .
RUN --mount=type=cache,target=/root/.m2 \
mvn dependency:go-offline -q
COPY src ./src
RUN --mount=type=cache,target=/root/.m2 \
mvn package -DskipTests
# Python — cache do pip
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
COPY . .O cache do --mount=type=cache persiste entre builds no host. Não fica na image final — é transparente.
--mount=type=secret — segredos sem expor em layers
Injeta segredos apenas durante o build sem que fiquem registrados em nenhuma layer da image.
# Dockerfile
FROM alpine
# Segredo disponível apenas durante esse RUN, não persiste na image
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm install
RUN --mount=type=secret,id=github_token \
GITHUB_TOKEN=$(cat /run/secrets/github_token) \
gh release download ...# Passar o segredo no build
docker buildx build \
--secret id=npmrc,src=$HOME/.npmrc \
--secret id=github_token,env=GITHUB_TOKEN \
-t myapp .--mount=type=ssh — acesso a repositórios git privados
FROM alpine
RUN apk add --no-cache openssh-client git
# Usa o agente SSH do host — chave nunca entra na image
RUN --mount=type=ssh \
git clone git@github.com:empresa/repo-privado.git /app# Iniciar ssh-agent e adicionar chave antes do build
eval $(ssh-agent)
ssh-add ~/.ssh/id_ed25519
docker buildx build --ssh default -t myapp .Multi-arch com docker buildx build --platform
# Criar e usar um builder multi-plataforma
docker buildx create --name multiarch --use
docker buildx inspect --bootstrap
# Build para múltiplas arquiteturas e push direto ao registry
docker buildx build \
--platform linux/amd64,linux/arm64,linux/arm/v7 \
--tag registro.example.com/myapp:1.0.0 \
--push \
.
# Build local (sem push) — somente uma plataforma por vez em --load
docker buildx build \
--platform linux/arm64 \
--tag myapp:arm64 \
--load \
.docker buildx bake — builds complexos declarativos
# docker-bake.hcl
variable "TAG" {
default = "latest"
}
group "default" {
targets = ["api", "worker", "frontend"]
}
target "api" {
context = "./api"
dockerfile = "Dockerfile"
platforms = ["linux/amd64", "linux/arm64"]
tags = ["registro.example.com/api:${TAG}"]
cache-from = ["type=registry,ref=registro.example.com/api:cache"]
cache-to = ["type=registry,ref=registro.example.com/api:cache,mode=max"]
}
target "worker" {
context = "./worker"
tags = ["registro.example.com/worker:${TAG}"]
}
target "frontend" {
context = "./frontend"
args = {
NODE_ENV = "production"
}
tags = ["registro.example.com/frontend:${TAG}"]
}# Executar todos os targets
TAG=1.2.0 docker buildx bake --push
# Executar target específico
docker buildx bake api --pushImage Signing com Docker Content Trust
Docker Content Trust (DCT)
DCT usa o Notary para assinar e verificar images. Quando habilitado, apenas images assinadas podem ser pulled/run.
# Habilitar DCT globalmente (afeta push e pull)
export DOCKER_CONTENT_TRUST=1
# Push assina automaticamente (cria chaves se não existirem)
docker push meuregistry.com/myapp:1.0.0
# Pull verifica assinatura — falha se não assinada
docker pull meuregistry.com/myapp:1.0.0
# Inspecionar confiança de uma image
docker trust inspect --pretty meuregistry.com/myapp:1.0.0
# Assinar image já existente no registry
docker trust sign meuregistry.com/myapp:1.0.0
# Adicionar signatário ao repositório
docker trust signer add --key cert.pem ci-bot meuregistry.com/myapp
# Revogar assinatura
docker trust revoke meuregistry.com/myapp:1.0.0Cosign — alternativa moderna (Sigstore)
Cosign é a solução recomendada atualmente para assinar imagens OCI. Não depende de Notary e integra com OIDC.
# Instalação
brew install cosign # macOS
go install github.com/sigstore/cosign/v2/cmd/cosign@latest
# Gerar par de chaves
cosign generate-key-pair
# Gera cosign.key (privada) e cosign.pub (pública)
# Assinar image (requer que a image já esteja no registry)
cosign sign --key cosign.key registro.example.com/myapp:1.0.0
# Verificar assinatura
cosign verify --key cosign.pub registro.example.com/myapp:1.0.0
# Assinar sem chave local — usando OIDC (keyless signing)
# Exige acesso ao Fulcio/Rekor (infra Sigstore pública ou self-hosted)
COSIGN_EXPERIMENTAL=1 cosign sign registro.example.com/myapp:1.0.0Assinatura em CI/CD com cosign
# .github/workflows/release.yml
- name: Build and push
uses: docker/build-push-action@v5
with:
push: true
tags: registro.example.com/myapp:${{ github.sha }}
- name: Sign image with cosign
env:
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
run: |
cosign sign --key env://COSIGN_PRIVATE_KEY \
registro.example.com/myapp:${{ github.sha }}
- name: Verify before deploy
run: |
cosign verify --key cosign.pub \
registro.example.com/myapp:${{ github.sha }}# Verificar policy em produção com cosign policy
cosign verify \
--certificate-identity "https://github.com/org/repo/.github/workflows/release.yml@refs/heads/main" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
registro.example.com/myapp:1.0.0Segurança — seccomp e AppArmor
Seccomp — filtragem de syscalls
Seccomp (Secure Computing Mode) cria um filtro de chamadas de sistema que o container pode fazer. O Docker tem um perfil padrão que bloqueia ~44 syscalls perigosas.
# Ver perfil seccomp padrão do Docker
docker run --rm -it ubuntu cat /proc/1/status | grep Seccomp
# Seccomp: 2 → 2 = FILTER (ativo), 0 = desabilitado
# Desabilitar seccomp (não recomendado em produção)
docker run --security-opt seccomp=unconfined myimage
# Usar perfil customizado
docker run --security-opt seccomp=/path/to/profile.json myimage// profile.json — perfil seccomp customizado (baseado no padrão + bloqueios extras)
{
"defaultAction": "SCMP_ACT_ERRNO",
"syscalls": [
{
"names": [
"read", "write", "open", "close", "stat", "fstat",
"mmap", "mprotect", "munmap", "brk", "rt_sigaction",
"rt_sigprocmask", "ioctl", "access", "pipe", "select",
"sched_yield", "mremap", "msync", "mincore", "madvise",
"dup", "dup2", "nanosleep", "getpid", "socket", "connect",
"accept", "sendto", "recvfrom", "bind", "listen",
"getsockname", "getpeername", "socketpair", "setsockopt",
"getsockopt", "clone", "fork", "execve", "exit", "wait4",
"kill", "getdents", "getcwd", "chdir", "mkdir", "rmdir",
"unlink", "rename", "chmod", "chown", "getuid", "getgid",
"setuid", "setgid", "getgroups", "futex", "prctl", "arch_prctl",
"exit_group", "epoll_wait", "epoll_ctl", "tgkill", "openat",
"newfstatat", "pread64", "pwrite64", "sendmsg", "recvmsg"
],
"action": "SCMP_ACT_ALLOW"
}
]
}Syscalls bloqueadas pelo perfil padrão incluem: reboot, mount, umount, kexec_load, create_module, init_module, delete_module, ptrace (parcialmente), perf_event_open, entre outras.
AppArmor — controle de acesso mandatório (MAC)
AppArmor restringe o que o processo pode acessar no sistema de arquivos, rede e capabilities — em nível de kernel.
# Docker aplica o perfil "docker-default" automaticamente em sistemas Linux com AppArmor
docker run --security-opt apparmor=docker-default myimage
# Desabilitar AppArmor (apenas para debug)
docker run --security-opt apparmor=unconfined myimage
# Usar perfil customizado
# 1. Criar o perfil
cat > /etc/apparmor.d/containers/myapp-profile << 'EOF'
#include <tunables/global>
profile myapp-profile flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
network inet tcp,
network inet udp,
deny /etc/shadow r,
deny /root/** rw,
/app/** r,
/tmp/** rw,
}
EOF
# 2. Carregar o perfil
apparmor_parser -r -W /etc/apparmor.d/containers/myapp-profile
# 3. Usar no container
docker run --security-opt apparmor=myapp-profile myimageCapabilities — princípio do menor privilégio
Linux divide os privilégios de root em ~40 capabilities granulares. Por padrão, o Docker mantém um subconjunto pequeno.
# Remover TODAS as capabilities e adicionar apenas o necessário
docker run \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \ # bind em portas < 1024
myimage
# Capabilities mais comuns e seus usos:
# NET_BIND_SERVICE → bind em portas privilegiadas (< 1024)
# NET_ADMIN → configuração de rede (iptables, interfaces)
# SYS_PTRACE → debug de processos (strace, gdb)
# SYS_ADMIN → operações de sistema (mount, namespaces) — muito amplo, evitar
# CHOWN → mudar ownership de arquivos
# DAC_OVERRIDE → ignorar permissões de arquivos
# SETUID / SETGID → trocar uid/gid do processo
# Ver capabilities atuais do container
docker run --rm ubuntu capsh --printRootless Docker
Rootless Docker executa o daemon e containers sem privilégios de root no host. Recomendado para ambientes multi-usuário.
# Instalar rootless Docker
dockerd-rootless-setuptool.sh install
# Usar rootless Docker
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
docker run hello-world
# Configurar como serviço do usuário (systemd)
systemctl --user enable docker
systemctl --user start dockerInstrução USER no Dockerfile
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY target/app.jar app.jar
# Criar usuário não-root dedicado
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# Ajustar permissões antes de trocar de usuário
RUN chown -R appuser:appgroup /app
# Trocar para usuário não-root — todas as instruções seguintes e o CMD rodam como appuser
USER appuser
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]# Verificar qual usuário o container está rodando
docker run --rm myimage whoami
# appuser ← não deve retornar root
# Inspecionar usuário configurado na image
docker inspect myimage --format '{{.Config.User}}'# docker-compose.yml — definir usuário em runtime
services:
api:
image: myapp:latest
user: "1001:1001" # uid:gid explícito
read_only: true # filesystem somente leitura
security_opt:
- no-new-privileges:true # impede escalada de privilégios via setuid
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE