Docker, Monorepo e Primeiro Deploy
Por que Docker? Containers e imagens
Você cria do zero os cinco serviços com manifestos de dependência, código mínimo e Dockerfiles — tudo que precisa para buildar e testar /health.
Nesta aula você vai
- Entender por que containers resolvem problemas de ambiente em monorepo multi-stack
- Criar pom.xml, requirements.txt, go.mod, Gemfile e package.json de cada serviço
- Validar build e execução local com endpoints de health
Por que Docker? Containers e imagens
Objetivos
- Entender por que containers resolvem problemas de ambiente em monorepo multi-stack
- Criar todos os arquivos necessários para buildar cada serviço (manifestos + código + Dockerfile)
- Validar build e execução local com endpoints de health
Pré-requisitos
- Docker instalado (
docker --version) - Pasta vazia para o projeto (ou clone do repositório do curso)
- Editor de texto e terminal
Conceito
Em um monorepo com Java, Python, Go, Ruby e Node, o problema não é só "rodar o código": é garantir que todos rodem com as mesmas versões, bibliotecas e comportamento em qualquer máquina. Docker empacota aplicação + runtime + dependências na imagem, tornando o ambiente parte versionada do projeto.
Importante: um Dockerfile sozinho não basta. Cada stack tem um manifesto de dependências (pom.xml, requirements.txt, etc.). Sem esses arquivos, o docker build falha. Nesta aula você cria tudo — do manifesto ao endpoint /health — para conseguir executar sem adivinhar o que falta.
Estrutura de arquivos
Ao final desta aula, você terá:
services/
customer-service/
pom.xml
Dockerfile
src/main/java/com/aprendi/customer/CustomerApplication.java
order-service/
requirements.txt
Dockerfile
app/main.py
payment-service/
go.mod
Dockerfile
cmd/api/main.go
notification-service/
Gemfile
Dockerfile
app.rb
analytics-service/
package.json
Dockerfile
src/server.js
Passo a passo
1. Criar a estrutura de pastas
mkdir -p services/customer-service/src/main/java/com/aprendi/customer
mkdir -p services/order-service/app
mkdir -p services/payment-service/cmd/api
mkdir -p services/notification-service
mkdir -p services/analytics-service/src
2. Criar manifestos de dependência
Cada serviço precisa declarar quais bibliotecas usa. Sem isso, Maven, pip, Go, Bundler e npm não sabem o que instalar.
2.1 — services/customer-service/pom.xml (Java + Spring Boot)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.2</version>
</parent>
<groupId>com.aprendi</groupId>
<artifactId>customer-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>customer-service</name>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
O spring-boot-starter-web traz o servidor embutido (Tomcat) e suporte a REST. O plugin Maven gera o .jar executável usado no Dockerfile.
2.2 — services/order-service/requirements.txt (Python + FastAPI)
fastapi==0.111.0
uvicorn[standard]==0.30.1
2.3 — services/payment-service/go.mod (Go + Gin)
module github.com/aprendi/payment-service
go 1.22
require github.com/gin-gonic/gin v1.10.0
Depois de criar o arquivo, gere o go.sum (checksums das dependências):
cd services/payment-service
go mod tidy
cd ../..
2.4 — services/notification-service/Gemfile (Ruby + Sinatra)
source "https://rubygems.org"
ruby "3.3.0"
gem "sinatra", "~> 4.0"
gem "puma", "~> 6.4"
Opcional: gere Gemfile.lock localmente com bundle install dentro da pasta do serviço. O Dockerfile abaixo funciona só com o Gemfile.
2.5 — services/analytics-service/package.json (Node + Express)
{
"name": "analytics-service",
"version": "1.0.0",
"private": true,
"main": "src/server.js",
"scripts": {
"start": "node src/server.js"
},
"dependencies": {
"express": "^4.19.2"
}
}
Gere o package-lock.json (recomendado para builds reproduzíveis):
cd services/analytics-service
npm install
cd ../..
3. Criar o código mínimo de cada serviço
3.1 — Customer (Java Spring)
Arquivo: services/customer-service/src/main/java/com/aprendi/customer/CustomerApplication.java
package com.aprendi.customer;
import java.util.Map;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
public class CustomerApplication {
public static void main(String[] args) {
SpringApplication.run(CustomerApplication.class, args);
}
}
@RestController
class HealthController {
@GetMapping("/health")
public Map<String, String> health() {
return Map.of("status", "ok", "service", "customer-service");
}
}
3.2 — Order (Python FastAPI)
Arquivo: services/order-service/app/main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/health")
def health():
return {"status": "ok", "service": "order-service"}
3.3 — Payment (Go Gin)
Arquivo: services/payment-service/cmd/api/main.go
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/health", func(c *gin.Context) {
c.JSON(200, gin.H{"status": "ok", "service": "payment-service"})
})
r.Run(":8080")
}
3.4 — Notification (Ruby Sinatra)
Arquivo: services/notification-service/app.rb
require "sinatra"
require "json"
set :port, 4567
set :bind, "0.0.0.0"
get "/health" do
content_type :json
{ status: "ok", service: "notification-service" }.to_json
end
3.5 — Analytics (Node Express)
Arquivo: services/analytics-service/src/server.js
const express = require("express");
const app = express();
app.get("/health", (_req, res) => {
res.json({ status: "ok", service: "analytics-service" });
});
app.listen(3000, "0.0.0.0", () => {
console.log("analytics-service running on :3000");
});
4. Criar Dockerfiles
Agora sim os Dockerfiles têm de onde copiar dependências e código.
4.1 — services/customer-service/Dockerfile
FROM maven:3.9.8-eclipse-temurin-21 AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn -q clean package -DskipTests
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
4.2 — services/order-service/Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app ./app
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
4.3 — services/payment-service/Dockerfile
FROM golang:1.22-alpine AS build
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o payment-api ./cmd/api
FROM alpine:3.20
WORKDIR /app
COPY --from=build /app/payment-api .
EXPOSE 8080
CMD ["./payment-api"]
4.4 — services/notification-service/Dockerfile
FROM ruby:3.3-alpine
WORKDIR /app
COPY Gemfile ./
RUN bundle install
COPY . .
EXPOSE 4567
CMD ["ruby", "app.rb"]
4.5 — services/analytics-service/Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY src ./src
EXPOSE 3000
CMD ["node", "src/server.js"]
Se ainda não tiver package-lock.json, troque a linha COPY por COPY package.json ./ e use RUN npm install --omit=dev.
5. Adicionar .dockerignore em cada serviço
Reduz o contexto de build e acelera o processo. Exemplo para services/customer-service/.dockerignore:
target/
.git
*.md
Repita em cada pasta de serviço (ignore node_modules/, __pycache__/, .bundle/, etc., conforme a stack).
Como testar
- Buildar cada serviço:
docker build -t aprendi/customer-service:local ./services/customer-service
docker build -t aprendi/order-service:local ./services/order-service
docker build -t aprendi/payment-service:local ./services/payment-service
docker build -t aprendi/notification-service:local ./services/notification-service
docker build -t aprendi/analytics-service:local ./services/analytics-service
- Rodar um container e validar
/health(exemplo com customer):
docker run --rm -d -p 18080:8080 --name customer aprendi/customer-service:local
curl -s http://localhost:18080/health
docker stop customer
Saída esperada:
{"status":"ok","service":"customer-service"}
- Testar os demais (portas sugeridas):
| Serviço | Comando run | curl |
|---|---|---|
| order | -p 18000:8000 |
http://localhost:18000/health |
| payment | -p 18081:8080 |
http://localhost:18081/health |
| notification | -p 14567:4567 |
http://localhost:14567/health |
| analytics | -p 13000:3000 |
http://localhost:13000/health |
Dicas de projeto
- Sempre crie o manifesto de dependências antes do Dockerfile — é a ordem natural de qualquer projeto real.
- Fixe versões nas imagens base (
python:3.12-slim, nãopython:latest). - Use
0.0.0.0como bind em todos os serviços;localhostdentro do container não aceita conexões externas. - Mantenha
.dockerignoreatualizado para builds rápidos.
Erros comuns
- Criar Dockerfile sem
pom.xml/requirements.txt→ build falha com "file not found". - Esquecer
go mod tidy→go.sumausente e etapago mod downloadquebra. - App escutando em
127.0.0.1→ health check ecurldo host falham. - Copiar
node_modulesdo host para a imagem → conflito de SO/arquitetura.
Resumo
Você criou os cinco serviços do zero: manifestos de dependência, código mínimo, Dockerfiles e .dockerignore. Com isso, qualquer pessoa consegue docker build e validar /health sem adivinhar o que falta. Nas próximas aulas, você organiza o monorepo e orquestra tudo com Compose.