Pular para o conteúdo
Resumos Técnicos

Guia Definitivo de Docker em Produção: Do Dockerfile ao Compose Seguro

Makis Jeanty · 16 de Agosto de 2026


Já mostrei [como subir o stack completo desta aplicação em uma VPS](/blog/do-zero-a-producao-django-docker-vps/). Este artigo é o complemento: um checklist de **segurança e resiliência** para o `Dockerfile` e o `docker-compose.yml` — as decisões que separam um compose "que funciona" de um compose pronto para produção.

---

## 1. Usuário não-root no container

Um container rodando como root que sofre um RCE entrega ao atacante um processo root com acesso ao kernel do host. A mitigação custa três linhas:

```dockerfile
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser
```

A partir do `USER appuser`, qualquer exploração da aplicação fica confinada a um usuário sem privilégios.

## 2. Segredos fail-closed no Compose

A sintaxe de interpolação do Compose aceita dois modificadores com comportamentos opostos:

- `${VAR:-default}` — se `.env` não define `VAR`, usa o default **silenciosamente**.
- `${VAR:?erro}` — se `.env` não define `VAR`, o compose **se recusa a subir**.

Para segredos, só a segunda é aceitável:

```yaml
environment:
SECRET_KEY: ${SECRET_KEY:?Defina SECRET_KEY no .env}
DB_PASSWORD: ${DB_PASSWORD:?Defina DB_PASSWORD no .env}
ALLOWED_HOSTS: ${ALLOWED_HOSTS:?Defina ALLOWED_HOSTS no .env}
```

O pior incidente aqui não é o deploy que falha — é o deploy que sobe com `SECRET_KEY=insecure-default` e ninguém percebe por seis meses. **Fail-closed**: na dúvida, quebre cedo e com mensagem clara.

Note o `ALLOWED_HOSTS` na lista: um `${ALLOWED_HOSTS:-*}` de conveniência desfaria, num único deploy, o hardening de host validation configurado no Django.

## 3. `.dockerignore` é controle de segurança, não organização

O `COPY . /app/` do Dockerfile copia **tudo** que não estiver ignorado — inclusive o `.env` com credenciais reais. Camadas de imagem são inspecionáveis por qualquer pessoa que consiga fazer pull:

```
# .dockerignore
.env
.git
media/
venv/
```

Sem essa exclusão, suas credenciais de produção ficam gravadas permanentemente nas camadas da imagem, mesmo que você delete o arquivo numa camada posterior.

## 4. Healthchecks e ordem de subida

`depends_on` sem condição só garante ordem de *início*, não de *prontidão* — o MySQL demora vários segundos entre o processo iniciar e aceitar conexões:

```yaml
db:
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -u root -p$${DB_ROOT_PASSWORD} || exit 1"]
interval: 5s
retries: 10

web:
depends_on:
db:
condition: service_healthy
```

Isso elimina a clássica corrida em que o Django tenta migrar antes do banco existir.

## 5. Rede interna: só o Nginx exposto

Num compose de produção, apenas o reverse proxy publica porta no host. Banco e Redis ficam acessíveis somente pela rede interna do Docker — nada de `3306:3306` "para facilitar o debug", que é como bancos acabam expostos na internet com senha fraca.

---

## Checklist final

1. `USER` não-root no Dockerfile.
2. Todo segredo com `${VAR:?erro}` — nunca `:-default`.
3. `.env` no `.dockerignore` (e no `.gitignore`).
4. Healthcheck no banco + `condition: service_healthy`.
5. Somente o proxy com portas publicadas.

Cada item custa minutos. A ausência de qualquer um deles já causou incidentes reais em produção mundo afora.

Comentários

Seja o primeiro a comentar.