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.

Avançado 60 min 25 pontos Leitura 0%

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

  1. 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
  1. 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"}
  1. 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ão python:latest).
  • Use 0.0.0.0 como bind em todos os serviços; localhost dentro do container não aceita conexões externas.
  • Mantenha .dockerignore atualizado para builds rápidos.

Erros comuns

  • Criar Dockerfile sem pom.xml/requirements.txt → build falha com "file not found".
  • Esquecer go mod tidygo.sum ausente e etapa go mod download quebra.
  • App escutando em 127.0.0.1 → health check e curl do host falham.
  • Copiar node_modules do 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.