DEV Community

Victor Zarzar
Victor Zarzar

Posted on • Edited on

# Otimizando Imagens Docker: Boas Práticas para Builds Eficientes

O Impacto de Imagens Docker Ineficientes

As imagens Docker são a base das aplicações containerizadas. No entanto, imagens grandes e ineficientes podem resultar em builds mais lentos, tempos maiores de implantação e aumento no uso de armazenamento. Otimizar imagens garante entregas mais rápidas, melhor desempenho e menor consumo de recursos.

Efeitos de Imagens Grandes na Implantação

Imagens pesadas não apenas consomem mais espaço em disco, mas também aumentam o tempo de transferência na rede, o que é crítico em pipelines CI/CD e implantações em nuvem. Imagens eficientes tornam as operações mais ágeis e reduzem custos operacionais.

1. Uso de Imagens Base Slim

Escolhendo Imagens Mínimas (Alpine, Variantes Slim)

Comece com uma imagem base mínima ou "slim" para reduzir o excesso de pacotes. Por exemplo, em vez de usar python:3.12, considere python:3.12-slim ou alpine. Imagens mínimas removem pacotes e bibliotecas desnecessárias, resultando em imagens menores e mais seguras.

Reduzindo Dependências Desnecessárias

Instale apenas os pacotes e bibliotecas indispensáveis para sua aplicação. Evite incluir ferramentas de build ou arquivos de documentação na imagem final de runtime.

Equilibrando Tamanho e Funcionalidade

Embora imagens slim sejam pequenas, algumas aplicações podem exigir bibliotecas específicas. Escolha uma imagem base que equilibre tamanho reduzido e funcionalidade necessária, evitando erros em tempo de execução.

2. Multi-Stage Builds

O Que São Multi-Stage Builds

Multi-stage builds permitem separar o ambiente de build do ambiente de runtime. Essa técnica possibilita incluir ferramentas de compilação em uma etapa e copiar apenas os artefatos finais para a imagem final, reduzindo drasticamente o tamanho.

Separando Ambientes de Build e Runtime

No multi-stage:

  • Stage 1: compila ou constrói a aplicação com todas as dependências necessárias
  • Stage 2: copia apenas o resultado final para uma imagem base menor, descartando ferramentas de build e arquivos intermediários

Exemplo prático (Bun)

# ============================================
# Stage 1: Dependencies Installation Stage
# ============================================

# This Dockerfile.bun is specifically configured for projects using Bun
# For npm/pnpm or yarn, refer to the Dockerfile instead

FROM oven/bun:1 AS dependencies

# Set working directory
WORKDIR /app

# Copy package-related files first to leverage Docker's caching mechanism
COPY package.json bun.lock* ./

# Install project dependencies with frozen lockfile for reproducible builds
RUN --mount=type=cache,target=/root/.bun/install/cache \
    bun install --no-save --frozen-lockfile

# ============================================
# Stage 2: Build Next.js application in standalone mode
# ============================================

FROM oven/bun:1 AS builder

# Set working directory
WORKDIR /app

# Copy project dependencies from dependencies stage
COPY --from=dependencies /app/node_modules ./node_modules

# Copy application source code
COPY . .

ENV NODE_ENV=production

# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED=1

# Build Next.js application
RUN bun run build

# ============================================
# Stage 3: Run Next.js application
# ============================================

FROM oven/bun:1 AS runner

# Set working directory
WORKDIR /app

# Set production environment variables
ENV NODE_ENV=production
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the run time.
# ENV NEXT_TELEMETRY_DISABLED=1

# Copy production assets
COPY --from=builder --chown=bun:bun /app/public ./public

# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown bun:bun .next

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=bun:bun /app/.next/standalone ./
COPY --from=builder --chown=bun:bun /app/.next/static ./.next/static

# If you want to persist the fetch cache generated during the build so that
# cached responses are available immediately on startup, uncomment this line:
# COPY --from=builder --chown=bun:bun /app/.next/cache ./.next/cache

# Switch to non-root user for security best practices
USER bun

# Expose port 3000 to allow HTTP traffic
EXPOSE 3000

# Start Next.js standalone server with Bun
CMD ["bun", "server.js"]
Enter fullscreen mode Exit fullscreen mode

Este modelo reduz bastante o tamanho final, eliminando ferramentas de build e reaproveitando cache de dependências via bun.

Exemplo com Python Slim

# =========================
# Stage 1 - builder
# =========================

FROM python:3.12-slim AS builder

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PIP_NO_CACHE_DIR=1 \
    PIP_DISABLE_PIP_VERSION_CHECK=1

RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

COPY pyproject.toml .

RUN pip install --upgrade pip setuptools wheel

COPY alembic.ini .
COPY entrypoint.sh .
COPY logger.yaml .
COPY app ./app
COPY alembic ./alembic

RUN pip install --no-cache-dir --no-compile .

# =========================
# Stage 2 - runtime
# =========================

FROM python:3.12-slim AS runtime

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PATH="/opt/venv/bin:$PATH"

RUN apt-get update && apt-get install -y --no-install-recommends \
    libpq5 \
    && rm -rf /var/lib/apt/lists/* \
    && adduser --disabled-password --gecos "" appuser \
    && mkdir -p /var/log/app \
    && chown -R appuser:appuser /var/log/app

COPY --from=builder /opt/venv /opt/venv
COPY --from=builder --chown=appuser:appuser /app /app

RUN chmod +x /app/entrypoint.sh

USER appuser

EXPOSE 8006

ENTRYPOINT ["/app/entrypoint.sh"]

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8006"]

Enter fullscreen mode Exit fullscreen mode

A imagem final permanece extremamente pequena, contendo apenas o runtime necessário.

3. Como as Camadas Docker Afetam o Tamanho da Imagem

Cada instrução no Dockerfile (RUN, COPY, ADD) cria uma camada. Apesar do cache, muitas camadas pequenas podem aumentar o tamanho total da imagem.

Uma forma de otimizar é combinar comandos relacionados em um único RUN:

RUN apt-get update && \
    apt-get install -y curl git && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*
Enter fullscreen mode Exit fullscreen mode

Isso gera uma única camada, mantendo a imagem menor.

Além disso, sempre remova caches e arquivos temporários dentro do mesmo comando RUN. Caso contrário, eles permanecem nas camadas anteriores e aumentam o tamanho final.

4. Otimização com .dockerignore

O arquivo .dockerignore impede que arquivos desnecessários sejam enviados ao contexto de build.

Exemplo básico:

node_modules
.next
.tests
__pycache__
__pytest__
builds/
venv
logs
Enter fullscreen mode Exit fullscreen mode

Isso reduz drasticamente o contexto enviado para o Docker, acelerando o build e evitando camadas desnecessárias.

5. Cache de Forma Eficiente

Podemos aproveitar o cache do Docker copiando primeiro os arquivos de dependências:

Para Bun:

COPY package.json bun.lock* ./

RUN --mount=type=cache,target=/root/.bun/install/cache \
    bun install --no-save --frozen-lockfile
Enter fullscreen mode Exit fullscreen mode

Para Python (Separar libs dev e prod num arquivo pyproject.toml):

COPY pyproject.toml .
COPY app ./app
COPY tests ./tests
COPY alembic.ini .
COPY entrypoint.sh .
COPY logger.yaml .

RUN pip install --no-cache-dir --upgrade pip && \
    pip install --no-cache-dir ".[dev]"
Enter fullscreen mode Exit fullscreen mode

Exemplo básico do pyproject.toml:

dependencies = [
  "fastapi==0.135.3",
  "uvicorn==0.44.0",
  "SQLAlchemy==2.0.49",
  "sqlmodel==0.0.38",
  "pydantic==2.13.0",
  "pydantic-settings==2.13.1",
  "pydantic[email]",
  "python-multipart==0.0.26",
  "pyaml==26.2.1",
  "slowapi==0.1.9",
  "alembic==1.18.4",
  "python-dotenv",
  "psycopg[binary]==3.3.3",
  "PyJWT==2.12.1",
  "pwdlib[argon2]",
  "cryptography==46.0.7",
  "redis==7.4.0",
  "rq==2.7.0",
  "aiokafka==0.13.0",
  "pyotp==2.9.0",
  "qrcode==8.2",
]

[project.optional-dependencies]
dev = [
  "psycopg2-binary",
  "pytest",
  "pytest-mock==3.15.1",
  "pytest-asyncio==1.3.0",
  "pytest-cov",
  "pylint==4.0.5",
  "ruff==0.15.10",
  "httpx==0.28.1",
]
Enter fullscreen mode Exit fullscreen mode

As dependências só serão reprocessadas quando realmente mudarem.

6. Boas Práticas Gerais

  • Use tags como DOCKER_TAG=1.0.0 ou IMAGE_VERSION=1.0.0 para rastrear versões e evitar confusão entre builds
  • Faça auditorias periódicas com docker image ls e remova imagens antigas
  • Integre otimizações no pipeline CI/CD para garantir builds consistentes, menores e mais rápidos
  • Evite ferramentas de teste e documentação na imagem final de produção
  • Prefira imagens minimalistas sempre que possível

Conclusão

Adotar essas boas práticas eleva o desempenho das aplicações, reduz custos operacionais e garante imagens compactas e eficientes para ambientes modernos e escaláveis. Multi-stage builds, uso de imagens slim, limpeza de camadas e .dockerignore são pilares essenciais para criar imagens com alta performance e fácil manutenção.

Top comments (0)