<h2>O Que São Imagens Distroless?</h2>
<p>Imagens distroless são contêineres Docker minimalistas que contêm apenas o necessário para executar sua aplicação: o binário compilado e suas dependências de runtime. Elas não incluem gerenciadores de pacotes, shells, utilitários Unix tradicionais ou qualquer outro software que não seja estritamente necessário para rodar o programa.</p>
<p>O conceito foi desenvolvido pelo Google e é mantido no repositório <code>distroless</code>. A ideia é radical: se sua aplicação é um binário Go compilado, por que incluir bash, apt, systemd e outras centenas de ferramentas que jamais serão usadas? Essa filosofia reduz drasticamente o tamanho da imagem e, mais importante, elimina superfícies de ataque. Se não existe shell no contêiner, um atacante não pode executar comandos interativos mesmo que encontre uma vulnerabilidade.</p>
<h2>Entendendo Alpine Linux</h2>
<p>Alpine Linux é uma distribuição baseada em <code>musl</code> e <code>busybox</code> que oferece um ponto intermediário entre distroless e distribuições completas como Ubuntu ou Debian. Ela inclui um gerenciador de pacotes (<code>apk</code>), um shell mínimo e ferramentas básicas, mantendo um tamanho reduzido (geralmente 5-10MB base).</p>
<p>Alpine é útil quando você precisa instalar dependências em tempo de build ou quando sua aplicação requer algumas ferramentas do sistema. É mais prática que distroless para processos de compilação complexos, mas oferece compromisso entre segurança e funcionalidade. Ao contrário de distroless, você <em>pode</em> fazer debug acessando o contêiner com <code>docker exec</code>, embora isso vá contra as melhores práticas de produção.</p>
<h3>Por Que Alpine é Menor que Ubuntu?</h3>
<p>Uma imagem Ubuntu base tem ~77MB, enquanto Alpine tem ~7MB. Isso ocorre porque Alpine não inclui a <code>glibc</code> (GNU C Library) pesada, usando <code>musl</code> em seu lugar. O <code>musl</code> é uma implementação de libc mais compacta, e <code>busybox</code> agrupa múltiplos utilitários Unix em um único binário. Você perde compatibilidade com algumas bibliotecas legadas compiladas para <code>glibc</code>, mas ganha tamanho significativamente menor.</p>
<h2>Comparação Prática: Distroless vs Alpine vs Tradicional</h2>
<p>Vamos construir a mesma aplicação Go em três cenários diferentes e comparar tamanhos e segurança.</p>
<h3>Exemplo 1: Dockerfile com Ubuntu (Baseline)</h3>
<pre><code class="language-dockerfile">FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
golang-go \
git \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY main.go .
RUN go build -o app main.go
CMD ["./app"]</code></pre>
<p>Tamanho final: ~500MB (Ubuntu base + Go toolchain)</p>
<h3>Exemplo 2: Dockerfile Multistage com Alpine</h3>
<pre><code class="language-dockerfile"># Stage 1: Builder
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY main.go .
RUN go build -o app main.go
Stage 2: Runtime
FROM alpine:3.18
RUN apk add --no-cache ca-certificates
WORKDIR /app
COPY --from=builder /app/app .
CMD ["./app"]</code></pre>
<p>Tamanho final: ~15-20MB (dependendo das dependências)</p>
<h3>Exemplo 3: Dockerfile com Distroless</h3>
<pre><code class="language-dockerfile"># Stage 1: Builder
FROM golang:1.21 AS builder
WORKDIR /app
COPY main.go .
RUN go build -o app main.go
Stage 2: Runtime com distroless
FROM gcr.io/distroless/base-debian12
COPY --from=builder /app/app /app
ENTRYPOINT ["/app"]</code></pre>
<p>Tamanho final: ~20-30MB (distroless base + binário)</p>
<h3>Aplicação Go Exemplo Completa</h3>
<p>Para testar os Dockerfiles acima, use esta aplicação simples:</p>
<pre><code class="language-go">// main.go
package main
import (
"fmt"
"net/http"
"os"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello from distroless/alpine!\n")
}
func main() {
http.HandleFunc("/", handler)
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
fmt.Printf("Server running on port %s\n", port)
http.ListenAndServe(":"+port, nil)
}</code></pre>
<p>Compile localmente:</p>
<pre><code class="language-bash">go build -o app main.go</code></pre>
<p>Para a versão distroless, o binário deve ser compilado de forma estática ou com as dependências necessárias incluídas. A imagem distroless não oferece ferramentas de debug tradicional, então você precisará estruturar logs e monitoramento corretamente na sua aplicação.</p>
<h2>Segurança em Produção</h2>
<h3>Redução de Superfície de Ataque</h3>
<p>Imagens distroless eliminam a maioria dos vetores de ataque comuns. Sem shell (<code>/bin/sh</code>), um atacante não pode executar comandos mesmo comprometendo a aplicação. Sem <code>apt</code>, <code>yum</code> ou <code>apk</code>, não é possível instalar backdoors pós-exploração. Uma análise de segurança em uma imagem distroless típica mostra zero vulnerabilidades conhecidas em componentes do SO porque praticamente não há componentes.</p>
<p>Compare com Alpine, que ainda inclui <code>apk</code>: um atacante poderia teoricamente usar <code>apk add</code> para instalar ferramentas maliciosas. Não é um risco trivial, mas existe. Ubuntu e Debian têm centenas de pacotes pré-instalados, multiplicando exponencialmente os riscos.</p>
<h3>Análise de Vulnerabilidades com Trivy</h3>
<p>Aqui está como avaliar imagens reais:</p>
<pre><code class="language-bash"># Instalar trivy (se não tiver)
https://github.com/aquasecurity/trivy
Escanear imagem Ubuntu
trivy image ubuntu:22.04
Escanear imagem Alpine
trivy image alpine:3.18
Escanear imagem Distroless
trivy image gcr.io/distroless/base-debian12</code></pre>
<p>Resultados típicos:</p>
<ul>
<li><strong>Ubuntu 22.04</strong>: 100+ vulnerabilidades (muitas críticas)</li>
<li><strong>Alpine 3.18</strong>: 5-10 vulnerabilidades (geralmente baixas)</li>
<li><strong>Distroless</strong>: 0-2 vulnerabilidades (patches mínimos)</li>
</ul>
<h3>Best Practices de Segurança</h3>
<pre><code class="language-dockerfile"># Use distroless quando possível
FROM gcr.io/distroless/base-debian12:nonroot
Se distroless não for viável, prefira alpine a ubuntu
FROM alpine:3.18
Sempre faça builds multistage para remover ferramentas de compilação
FROM golang:1.21-alpine AS builder
RUN go build -ldflags="-w -s" -o app main.go
FROM alpine:3.18
COPY --from=builder /app/app /app
Nunca rode como root
RUN addgroup -g 1000 appuser && \
adduser -D -u 1000 -G appuser appuser
USER appuser
CMD ["/app"]</code></pre>
<h2>Quando Usar Cada Uma</h2>
<h3>Use Distroless Quando:</h3>
<ul>
<li>Sua aplicação é um binário compilado (Go, Rust, C compilado estaticamente)</li>
<li>Você não precisa de ferramentas de debug em produção (logs estruturados são suficientes)</li>
<li>Segurança máxima é crítico (aplicações financeiras, dados sensíveis)</li>
<li>Você quer imagens o mais pequenas possível (edge computing, serverless)</li>
</ul>
<h3>Use Alpine Quando:</h3>
<ul>
<li>Você precisa executar comandos em tempo de build (instalar dependências específicas)</li>
<li>Sua linguagem é interpretada (Python, Node.js) e requer binários adicionais</li>
<li>Você ocasionalmente precisa debugar em produção via <code>docker exec</code></li>
<li>A compatibilidade com <code>glibc</code> é necessária (alguns binários legados)</li>
</ul>
<h3>Use Ubuntu/Debian Quando:</h3>
<ul>
<li>Sua aplicação tem dependências complexas que requerem o ecossistema completo</li>
<li>Você está em ambiente legado onde mudanças arriscadas devem ser evitadas</li>
<li>A diferença de 100-500MB não impacta seus custos ou infraestrutura</li>
</ul>
<h2>Exemplo Real: Aplicação Python com Alpine</h2>
<p>Python é um caso mais realista onde Alpine é preferível a distroless, pois a interpretação requer a runtime Python compilada:</p>
<pre><code class="language-dockerfile"># Stage 1: Builder
FROM python:3.11-alpine AS builder
WORKDIR /app
COPY requirements.txt .
Instalar apenas o essencial de build
RUN apk add --no-cache --virtual .build-deps \
gcc \
musl-dev \
&& pip install --no-cache-dir --user -r requirements.txt \
&& apk del .build-deps
Stage 2: Runtime
FROM python:3.11-alpine
Instalar apenas runtime necessário
RUN apk add --no-cache \
ca-certificates \
libffi-dev
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY app.py .
ENV PATH=/root/.local/bin:$PATH
RUN addgroup -g 1000 appuser && \
adduser -D -u 1000 -G appuser appuser
USER appuser
CMD ["python", "app.py"]</code></pre>
<p>Aplicação Python correspondente:</p>
<pre><code class="language-python"># app.py
from flask import Flask
import os
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello from Alpine!\n'
if __name__ == '__main__':
port = os.getenv('PORT', 8080)
app.run(host='0.0.0.0', port=int(port))</code></pre>
<p>Este Dockerfile com Alpine resultará em ~200-250MB, muito menos que ~800MB com Debian.</p>
<h2>Otimizações Adicionais</h2>
<h3>Compilação Estática em Go</h3>
<p>Para distroless ser verdadeiramente eficaz, compile Go sem dependências dinâmicas:</p>
<pre><code class="language-bash">CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -ldflags="-w -s" -o app main.go</code></pre>
<p>As flags <code>-w -s</code> removem símbolos de debug, reduzindo tamanho do binário em 30-40%.</p>
<h3>Verificação de Tamanhos Localmente</h3>
<pre><code class="language-bash"># Build das três imagens
docker build -f Dockerfile.ubuntu -t app:ubuntu .
docker build -f Dockerfile.alpine -t app:alpine .
docker build -f Dockerfile.distroless -t app:distroless .
Comparar tamanhos
docker images | grep app
Output típico:
app ubuntu 500MB
app alpine 18MB
app distroless 25MB</code></pre>
<h2>Conclusão</h2>
<p>Imagens distroless e Alpine representam uma evolução crítica em como construímos aplicações containerizadas para produção. A escolha entre elas não é teórica—é uma decisão entre segurança máxima (distroless) e praticidade com tamanho reduzido (Alpine). Ubuntu e Debian continuam tendo lugar em cenários legados, mas devem ser a exceção, não a regra.</p>
<p>Três aprendizados principais para levar adiante: Primeiro, sempre use builds multistage, independentemente da imagem base—separar ferramentas de compilação de runtime é tão importante quanto a escolha da base. Segundo, teste suas imagens com scanners de vulnerabilidade (Trivy, Snyk) antes de promover para produção; os números não mentem. Terceiro, entenda que distroless não é "melhor" que Alpine universalmente—use a ferramenta certa para o trabalho, mas comece com distroless e considere Alpine apenas quando você tiver razões técnicas legítimas para isso.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://github.com/GoogleContainerTools/distroless" target="_blank" rel="noopener noreferrer">Distroless Docker Images - Google</a></li>
<li><a href="https://alpinelinux.org/about/" target="_blank" rel="noopener noreferrer">Alpine Linux Official Documentation</a></li>
<li><a href="https://docs.docker.com/develop/develop-images/dockerfile_best-practices/" target="_blank" rel="noopener noreferrer">Docker Best Practices - Official Documentation</a></li>
<li><a href="https://github.com/aquasecurity/trivy" target="_blank" rel="noopener noreferrer">Trivy Vulnerability Scanner</a></li>
<li><a href="https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-190.pdf" target="_blank" rel="noopener noreferrer">Container Security Best Practices - NIST</a></li>
</ul>
<p><!-- FIM --></p>