🏠 TAREFA PARA CASA: Polyglot Full-Stack com Docker Compose¶
⏱️ Tempo estimado: 2–3h | 🎯 Foco: Orquestração multi-serviço, CORS, volumes, redes internas
📐 Arquitetura¶
[Frontend:8080] ──CORS──> 🌐 Node API :3000 ──> 📦 PostgreSQL :5432
🌐 Python API :8000 ──> 📦 PostgreSQL :5432 (compartilhado)
- Frontend: HTML/JS estático servido via Nginx
- Backend 1 (Node.js): CRUD de
todos - Backend 2 (Python): Leitura de estatísticas do mesmo banco
- Banco: PostgreSQL com volume nomeado
✅ Objetivo Docker: Containerizar 4 serviços, resolver comunicação entre portas diferentes (CORS), garantir persistência e usar
docker composepara orquestrar tudo com um comando.
📁 Estrutura Obrigatória¶
polyglot-compose/
├── docker-compose.yml
├── frontend/
│ ├── Dockerfile
│ └── index.html
├── node-api/
│ ├── Dockerfile
│ ├── package.json
│ └── index.js
├── python-svc/
│ ├── Dockerfile
│ ├── requirements.txt
│ └── main.py
└── README.md
💻 Código Pronto para Copiar¶
🌐 frontend/index.html (Vanilla JS)¶
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>Polyglot Docker App</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 800px; margin: 2rem auto; padding: 0 1rem; background: #f8f9fa; }
.card { background: #fff; border: 1px solid #ddd; padding: 1.5rem; margin: 1rem 0; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); }
input, button { padding: 0.5rem; margin: 0.3rem 0; }
button { cursor: pointer; background: #0d6efd; color: white; border: none; border-radius: 4px; }
ul { list-style: none; padding: 0; }
li { padding: 0.4rem 0; border-bottom: 1px solid #eee; }
</style>
</head>
<body>
<h1>🐳 App Polyglot (Node + Python + Docker)</h1>
<div class="card">
<h2>📝 Gerenciar Todos (Node.js API)</h2>
<input id="todoInput" placeholder="Nova tarefa...">
<button onclick="addTodo()">Adicionar</button>
<ul id="todoList"></ul>
</div>
<div class="card">
<h2>📊 Estatísticas (Python FastAPI)</h2>
<button onclick="loadStats()">Atualizar Stats</button>
<p id="statsOutput">Clique para carregar...</p>
</div>
<script>
const NODE_API = 'http://localhost:3000';
const PYTHON_API = 'http://localhost:8000';
async function loadTodos() {
const res = await fetch(`${NODE_API}/todos`);
const todos = await res.json();
document.getElementById('todoList').innerHTML = todos.map(t => `<li>${t.title}</li>`).join('');
}
async function addTodo() {
const title = document.getElementById('todoInput').value;
await fetch(`${NODE_API}/todos`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title })
});
document.getElementById('todoInput').value = '';
loadTodos();
}
async function loadStats() {
const res = await fetch(`${PYTHON_API}/stats`);
const stats = await res.json();
document.getElementById('statsOutput').innerText =
`Total de todos: ${stats.total_todos} | Tamanho médio do título: ${stats.avg_title_length.toFixed(1)} caracteres`;
}
window.onload = loadTodos;
</script>
</body>
</html>
🟢 node-api/package.json (adicionado cors)¶
{
"name": "node-api",
"version": "1.0.0",
"main": "index.js",
"dependencies": { "express": "^4.18.2", "pg": "^8.11.3", "cors": "^2.8.5" }
}
🟢 node-api/index.js (adicionado cors())¶
const express = require('express');
const cors = require('cors');
const { Pool } = require('pg');
const app = express();
app.use(cors()); // ✅ Permite requisições do frontend
app.use(express.json());
const pool = new Pool({
host: process.env.DB_HOST,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
});
app.get('/todos', async (_, res) => {
const { rows } = await pool.query('SELECT * FROM todos ORDER BY id DESC');
res.json(rows);
});
app.post('/todos', async (req, res) => {
const { title } = req.body;
const { rows } = await pool.query('INSERT INTO todos (title) VALUES ($1) RETURNING *', [title]);
res.status(201).json(rows[0]);
});
pool.query(`CREATE TABLE IF NOT EXISTS todos (id SERIAL PRIMARY KEY, title TEXT, created_at TIMESTAMP DEFAULT NOW())`)
.then(() => app.listen(3000, () => console.log('✅ Node API rodando na porta 3000')));
🐍 python-svc/main.py (adicionado CORS)¶
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import psycopg2, os
app = FastAPI()
# ✅ Permite requisições do frontend
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
def get_db():
return psycopg2.connect(
host=os.getenv("DB_HOST"),
database=os.getenv("DB_NAME"),
user=os.getenv("DB_USER"),
password=os.getenv("DB_PASSWORD")
)
@app.get("/stats")
def stats():
conn = get_db()
cur = conn.cursor()
cur.execute("SELECT COUNT(*), AVG(LENGTH(title)) FROM todos")
row = cur.fetchone()
return {"total_todos": row[0], "avg_title_length": float(row[1]) if row[1] else 0}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
🐍 python-svc/Dockerfile & requirements.txt¶
requirements.txt
Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0"]
🌐 frontend/Dockerfile (Nginx estático)¶
FROM nginx:alpine
COPY index.html /usr/share/nginx/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
🛠️ docker-compose.yml (Esqueleto para completar)¶
⚠️ Desafio: Preencha os
_________usando os conceitos do Encontro 1.
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: polyglot
POSTGRES_PASSWORD: secret
volumes:
- _________:/var/lib/postgresql/data
node-api:
build: ./node-api
ports: ["3000:3000"]
environment:
DB_HOST: db
DB_USER: postgres
DB_PASSWORD: secret
DB_NAME: polyglot
depends_on:
- db
python-svc:
build: ./python-svc
ports: ["_________:8000"]
environment:
DB_HOST: _________
DB_USER: postgres
DB_PASSWORD: secret
DB_NAME: polyglot
depends_on:
- db
frontend:
build: ./frontend
ports: ["8080:80"]
depends_on:
- node-api
- python-svc
volumes:
_________
✅ Instruções de Execução & Teste¶
- Na raiz:
docker compose up -d --build - Aguarde ~10s. Abra
http://localhost:8080 - Adicione um todo → clique em "Atualizar Stats" → veja a contagem aumentar.
- Teste persistência:
docker compose down && docker compose up -d→ os dados devem permanecer. - Verifique saúde:
docker compose ps→ todos devem estarUp.
📋 Critérios de Aceite¶
- [ ]
docker compose up -dsobe 4 serviços sem erros - [ ] Frontend responde em
localhost:8080 - [ ] Node API em
3000, Python em8000 - [ ] Ambos acessam o mesmo banco e tabela
- [ ] Dados persistem após
down/up - [ ] CORS configurado corretamente (sem erros no console do navegador)
- [ ]
README.mdcom instruções de uso e arquitetura
🩺 Tabela de Debug (Entregue com a tarefa)¶
| Sintoma | Causa Provável | Como investigar |
|---|---|---|
Blocked by CORS policy |
Backend sem CORS ou frontend usando IP errado | docker compose logs node-api → adicione app.use(cors()) |
fetch failed ou Network Error |
Frontend tentando http://localhost dentro do container Nginx |
Nginx roda no container, mas o JS roda no navegador. localhost aponta para a máquina host. ✅ Correto! |
Python ModuleNotFoundError |
requirements.txt não copiado ou pip install falhou |
docker compose build python-svc → veja output do build |
DB connection refused |
DB_HOST errado ou Postgres ainda iniciando |
docker compose logs db \| grep "ready" |
| Frontend 404 | COPY no Dockerfile apontando para arquivo errado |
docker compose exec frontend ls /usr/share/nginx/html |