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"]
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"]
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/*
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
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
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]"
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",
]
As dependências só serão reprocessadas quando realmente mudarem.
6. Boas Práticas Gerais
- Use tags como
DOCKER_TAG=1.0.0ouIMAGE_VERSION=1.0.0para rastrear versões e evitar confusão entre builds - Faça auditorias periódicas com
docker image lse 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)