<h2>Docker Compose: Ambientes Multi-container e Variáveis de Ambiente</h2>
<p>Docker Compose é uma ferramenta que permite definir e executar aplicações multi-container de forma declarativa através de um arquivo YAML. Em vez de executar vários comandos <code>docker run</code> manualmente, você descreve toda a infraestrutura em um único arquivo, facilitando o desenvolvimento, testes e deploy. Isso é especialmente útil quando sua aplicação depende de vários serviços: banco de dados, cache, fila de mensagens, API, etc.</p>
<p>O principal benefício é a reprodutibilidade. Qualquer desenvolvedor pode clonar seu repositório, executar <code>docker-compose up</code> e ter exatamente o mesmo ambiente rodando localmente. Isso elimina aquele clássico problema: "funciona na minha máquina" — agora funciona em todas as máquinas que têm Docker instalado.</p>
<h3>Instalação e Primeiros Passos</h3>
<p>Se você já tem Docker Desktop instalado (Windows ou macOS), Docker Compose já vem incluído. No Linux, você precisará instalar separadamente. Verifique a versão com:</p>
<pre><code class="language-bash">docker-compose --version</code></pre>
<p>A estrutura básica de um projeto com Compose é simples: você cria um arquivo chamado <code>docker-compose.yml</code> na raiz do projeto. Dentro dele, você define quais serviços (containers) sua aplicação precisa e como eles devem ser configurados.</p>
<h2>Estrutura e Sintaxe do docker-compose.yml</h2>
<p>O arquivo <code>docker-compose.yml</code> usa a versão YAML e organiza-se em seções principais. Vamos entender cada uma antes de ver exemplos práticos.</p>
<h3>Versão e Estrutura Básica</h3>
<pre><code class="language-yaml">version: '3.9'
services:
web:
image: nginx:latest
ports:
- "80:80"
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: senha123</code></pre>
<p>A versão <code>3.9</code> é uma das mais estáveis e compatíveis atualmente. A seção <code>services</code> é onde você define cada container que sua aplicação precisa. Cada serviço recebe um nome (neste caso, <code>web</code> e <code>db</code>) que se torna o hostname para comunicação entre containers.</p>
<h3>Configurações Essenciais de um Serviço</h3>
<p>Cada serviço pode ter múltiplas configurações. As mais importantes são: <code>image</code> (qual imagem Docker usar), <code>ports</code> (mapeamento de portas), <code>volumes</code> (persistência de dados), <code>environment</code> (variáveis de ambiente) e <code>depends_on</code> (dependências entre serviços).</p>
<pre><code class="language-yaml">version: '3.9'
services:
app:
build: .
container_name: minha_app
ports:
- "3000:3000"
volumes:
- ./src:/app/src
- /app/node_modules
environment:
- NODE_ENV=development
- DEBUG=true
depends_on:
- database
command: npm start
restart: unless-stopped
database:
image: postgres:15-alpine
container_name: postgres_db
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_USER: admin
POSTGRES_PASSWORD: senha_segura
POSTGRES_DB: minha_app_db
volumes:
postgres_data:</code></pre>
<p>Note que <code>build: .</code> instrui o Docker Compose a construir a imagem a partir do <code>Dockerfile</code> no diretório atual, enquanto <code>image:</code> usa uma imagem pré-existente. O <code>depends_on</code> garante que o banco de dados inicie antes da aplicação. A seção <code>volumes</code> ao final define volumes nomeados que persistem dados mesmo após os containers serem destruídos.</p>
<h2>Variáveis de Ambiente: Configuração Dinâmica</h2>
<p>Variáveis de ambiente são fundamentais para que sua aplicação funcione em diferentes contextos (desenvolvimento, teste, produção) sem alterar o código. Docker Compose oferece várias maneiras de gerenciá-las.</p>
<h3>Métodos de Definir Variáveis</h3>
<p>O primeiro método é direto no <code>docker-compose.yml</code> usando a chave <code>environment</code>. Você pode defini-las como uma lista ou como um dicionário:</p>
<pre><code class="language-yaml">version: '3.9'
services:
api:
image: node:18-alpine
environment:
- DATABASE_HOST=database
- DATABASE_PORT=5432
- DATABASE_USER=app_user
- LOG_LEVEL=info</code></pre>
<p>Porém, colocar valores sensíveis (senhas, tokens) diretamente no <code>docker-compose.yml</code> é uma má prática de segurança, especialmente se você versionará esse arquivo no Git. O segundo método usa um arquivo <code>.env</code>:</p>
<pre><code class="language-bash"># .env
DATABASE_PASSWORD=senha_super_secreta
API_TOKEN=abc123xyz789
REDIS_HOST=redis
DEBUG=false</code></pre>
<p>No <code>docker-compose.yml</code>, você referencia essas variáveis com <code>${NOME_DA_VARIAVEL}</code>:</p>
<pre><code class="language-yaml">version: '3.9'
services:
app:
image: app:latest
environment:
- DATABASE_PASSWORD=${DATABASE_PASSWORD}
- API_TOKEN=${API_TOKEN}
- REDIS_HOST=${REDIS_HOST}
- DEBUG=${DEBUG}
database:
image: postgres:15
environment:
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}</code></pre>
<p>Docker Compose carrega automaticamente variáveis do arquivo <code>.env</code> se ele estiver no mesmo diretório do <code>docker-compose.yml</code>. Lembre-se: <strong>adicione <code>.env</code> ao seu <code>.gitignore</code></strong> para não expor dados sensíveis no repositório.</p>
<h3>Arquivo .env para Diferentes Ambientes</h3>
<p>Para ambientes diferentes, você pode usar múltiplos arquivos <code>.env</code> e especificá-los com a flag <code>--env-file</code>:</p>
<pre><code class="language-bash">docker-compose --env-file .env.development up
docker-compose --env-file .env.production up</code></pre>
<p>Ou simplesmente renomeie os arquivos e use o padrão:</p>
<pre><code class="language-yaml"># docker-compose.yml
version: '3.9'
services:
web:
image: app:latest
environment:
- APP_ENV=${APP_ENV:-development}
- DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}</code></pre>
<p>A sintaxe <code>${VARIAVEL:-valor_padrao}</code> define um valor padrão caso a variável não esteja definida. Isso torna seu Compose mais robusto e menos propenso a erros.</p>
<h2>Exemplo Prático Completo: Stack Web com PostgreSQL, Redis e Node.js</h2>
<p>Vamos criar um exemplo real funcionando. Este projeto terá uma aplicação Node.js, um banco de dados PostgreSQL e um cache Redis.</p>
<h3>Estrutura de Pastas</h3>
<pre><code>projeto/
├── docker-compose.yml
├── .env
├── .env.example
├── Dockerfile
├── src/
│ ├── index.js
│ ├── package.json
│ └── package-lock.json
└── .gitignore</code></pre>
<h3>Arquivo .env.example (para documentação)</h3>
<pre><code class="language-bash"># Copie este arquivo para .env e preencha os valores
APP_ENV=development
NODE_ENV=development
PORT=3000
Database
DB_HOST=postgres
DB_PORT=5432
DB_USER=app_user
DB_PASSWORD=senha_dev_123
DB_NAME=app_database
Redis
REDIS_HOST=redis
REDIS_PORT=6379</code></pre>
<h3>Dockerfile</h3>
<pre><code class="language-dockerfile">FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]</code></pre>
<h3>docker-compose.yml</h3>
<pre><code class="language-yaml">version: '3.9'
services:
app:
build:
context: .
dockerfile: Dockerfile
container_name: app_web
ports:
- "${PORT:-3000}:3000"
environment:
- NODE_ENV=${NODE_ENV}
- APP_ENV=${APP_ENV}
- DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}
- REDIS_URL=redis://${REDIS_HOST}:${REDIS_PORT}
- LOG_LEVEL=debug
volumes:
- ./src:/app/src
- /app/node_modules
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
restart: unless-stopped
networks:
- app-network
postgres:
image: postgres:15-alpine
container_name: app_postgres
ports:
- "5432:5432"
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_NAME}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- app-network
redis:
image: redis:7-alpine
container_name: app_redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
restart: unless-stopped
networks:
- app-network
volumes:
postgres_data:
redis_data:
networks:
app-network:
driver: bridge</code></pre>
<h3>Arquivo src/index.js (Exemplo de Aplicação)</h3>
<pre><code class="language-javascript">const express = require('express');
const { Pool } = require('pg');
const redis = require('redis');
const app = express();
const port = process.env.PORT || 3000;
// Conexão PostgreSQL
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
// Conexão Redis
const redisClient = redis.createClient({
url: process.env.REDIS_URL,
});
redisClient.on('error', (err) => console.log('Redis Error:', err));
redisClient.connect();
app.get('/health', (req, res) => {
res.json({ status: 'ok', environment: process.env.APP_ENV });
});
app.get('/users', async (req, res) => {
try {
const cached = await redisClient.get('users');
if (cached) {
return res.json({ source: 'cache', data: JSON.parse(cached) });
}
const result = await pool.query('SELECT * FROM users LIMIT 10');
await redisClient.setEx('users', 3600, JSON.stringify(result.rows));
res.json({ source: 'database', data: result.rows });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.listen(port, () => {
console.log(App rodando em http://localhost:${port});
console.log(Ambiente: ${process.env.APP_ENV});
});</code></pre>
<h3>package.json</h3>
<pre><code class="language-json">{
"name": "app",
"version": "1.0.0",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js"
},
"dependencies": {
"express": "^4.18.2",
"pg": "^8.10.0",
"redis": "^4.6.7"
}
}</code></pre>
<h3>Comandos para Executar</h3>
<pre><code class="language-bash"># Criar arquivo .env a partir do exemplo
cp .env.example .env
Compilar imagens e iniciar todos os containers
docker-compose up -d
Verificar status
docker-compose ps
Ver logs da aplicação
docker-compose logs -f app
Executar comando dentro de um container
docker-compose exec app npm install
Parar todos os containers
docker-compose down
Parar e remover volumes (cuidado!)
docker-compose down -v</code></pre>
<h2>Boas Práticas e Troubleshooting</h2>
<h3>Segurança</h3>
<p>Nunca commite o arquivo <code>.env</code> com dados reais no repositório. Use <code>.env.example</code> como template. Para produção, use secrets management como Docker Secrets (em Swarm) ou use variáveis de ambiente do seu orquestrador (Kubernetes, por exemplo).</p>
<pre><code class="language-bash"># .gitignore
.env
.env.local
*.log</code></pre>
<h3>Healthchecks</h3>
<p>Use <code>healthcheck</code> para garantir que os containers estão genuinamente prontos antes de iniciar dependências. No exemplo anterior, o PostgreSQL só é considerado "ready" quando responde ao comando <code>pg_isready</code>.</p>
<h3>Isolamento de Rede</h3>
<p>Defina uma rede customizada (<code>app-network</code> no exemplo) para seus containers. Isso oferece melhor isolamento e permite que os serviços se comuniquem pelo nome do serviço sem expor portas desnecessariamente.</p>
<h3>Debugging Comum</h3>
<p>Se sua aplicação não consegue conectar ao banco de dados:</p>
<ol>
<li>Verifique se o banco de dados iniciou corretamente: <code>docker-compose logs postgres</code></li>
<li>Teste a conectividade dentro do container: <code>docker-compose exec app ping postgres</code></li>
</ol>
<p>3. Confirme se as variáveis de ambiente estão corretas: <code>docker-compose exec app env | grep DATABASE</code></p>
<h2>Conclusão</h2>
<p>Você aprendeu que <strong>Docker Compose transforma a complexidade de ambientes multi-container em um arquivo YAML simples e versionável</strong>, eliminando problemas de reprodutibilidade. O segundo ponto essencial é que <strong>variáveis de ambiente são o mecanismo correto para configuração dinâmica</strong>, permitindo que o mesmo código rode em desenvolvimento, teste e produção sem alterações. Por fim, <strong>healthchecks, redes customizadas e volumes nomeados</strong> são práticas que transformam seu Compose de um experimento local em um setup robusto, seguro e escalável.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://docs.docker.com/compose/" target="_blank" rel="noopener noreferrer">Docker Compose Official Documentation</a></li>
<li><a href="https://docs.docker.com/compose/compose-file/" target="_blank" rel="noopener noreferrer">Docker Compose File Format Reference</a></li>
<li><a href="https://docs.docker.com/compose/environment-variables/" target="_blank" rel="noopener noreferrer">Environment Variables in Compose</a></li>
<li><a href="https://docs.docker.com/network/" target="_blank" rel="noopener noreferrer">Docker Networking Guide</a></li>
<li><a href="https://hub.docker.com/_/postgres" target="_blank" rel="noopener noreferrer">PostgreSQL Docker Image Documentation</a></li>
</ul>
<p><!-- FIM --></p>