Skip to content

Repository files navigation

Atividade Docker + CI — Kelvin Araújo Ferreira

Atividade prática do Módulo 11 — DevOps e Cloud Computing (Capacitação em Desenvolvimento Full Stack, iTeam), sobre containerização, orquestração com Docker Compose e Integração Contínua. Enunciado completo, contexto do curso e anotações de aula em iteam-devops-modulo11/atividade-docker-ci — o repositório de anotações do módulo.

Aluno(a): Kelvin Araújo Ferreira Turma: Noturno Data: 24/07/2026 Aplicação usada: docker/getting-started-app — To-Do em Node.js

1. Como executar este projeto

Pré-requisito: ter o Docker instalado e rodando na máquina.

git clone https://github.com/DilliKel/getting-started-docker.git
cd getting-started-docker
cp .env.example .env
docker compose up -d --build

Isso builda a imagem da aplicação e sobe dois containers: o app (a aplicação To-Do) e o db (MySQL). Pode levar alguns segundos até o banco ficar pronto — dá pra acompanhar com docker compose ps (o db precisa aparecer como healthy).

Depois de subir, abra o navegador e acesse:

http://localhost:3000

Você vai ver a interface do To-Do app — dá pra cadastrar, marcar como concluída e excluir tarefas. Elas ficam salvas no banco MySQL, dentro do volume todo-mysql-data.

Para derrubar a aplicação:

  • docker compose down — para e remove os containers, mantém os dados (o volume continua existindo)
  • docker compose down -v — para tudo e apaga os dados (remove o volume também)

2. Imagem e Dockerfile multi-stage

Estágios utilizados: builder (instala as dependências com npm ci --omit=dev) e o estágio final (copia apenas node_modules + src, sem ferramentas de build). Imagem base: node:20-alpine Usuário de execução: node (não-root, já vem pronto na imagem node:alpine) Tamanho final da imagem: ~58MB

Por que o multi-stage ajuda? Porque o estágio final só recebe o node_modules já pronto e o código-fonte — nada das ferramentas de build, cache do npm ou arquivos temporários do estágio builder vai parar na imagem final. Isso deixa a imagem menor (menos superfície de ataque, menos coisa pra escanear em busca de vulnerabilidade) e mais rápida de baixar/subir em produção.

Print 1docker build + docker images Build e tamanho da imagem

Print 2 — aplicação rodando com tarefas cadastradas App rodando

3. Volumes e persistência

Volume usado: todo-db → montado em /etc/todos (container avulso, Parte 2) — no Compose, o volume equivalente é todo-mysql-data/var/lib/mysql

Print 3 — SEM volume: dados perdidos ao recriar o container Sem volume

Print 4 — COM volume: dados preservados Com volume

Diferença entre docker compose down e docker compose down -v: down para e remove os containers e a rede, mas mantém os volumes nomeados (os dados sobrevivem); down -v faz tudo isso e também apaga os volumes, perdendo os dados de vez.

4. Rede

Rede criada: todo-net Serviços conectados: app e mysql/db A porta do banco está exposta ao host? Não — só o app precisa conversar com o banco, e essa comunicação já acontece dentro da rede Docker (todo-net); publicar a porta 3306 no host abriria o MySQL pra qualquer coisa rodando na máquina (ou na rede, dependendo do firewall), sem necessidade nenhuma.

Por que o app consegue chamar o host mysql/db sem saber o IP? Porque containers na mesma rede Docker (definida pelo usuário, seja via docker network create ou automaticamente pelo Compose) resolvem uns aos outros pelo nome — o Docker mantém um DNS interno que traduz o nome do serviço/container pro IP real, então o app só precisa saber o nome (mysql ou db), nunca o IP.

Print 5docker network inspect (em duas partes, para caber tudo) Network inspect - parte 1 Network inspect - parte 2

Print 6 — dados dentro do MySQL (select * from todo_items;) Select no MySQL

5. Docker Compose

Serviços: app, db Rede: todo-net · Volume: todo-mysql-data Healthcheck em: db (mysqladmin ping) · depends_on com: condition: service_healthy Variáveis sensíveis: carregadas via .env (não versionado). Modelo em .env.example.

Print 7docker compose ps Compose ps

6. Integração Contínua (GitHub Actions)

Arquivo do workflow: .github/workflows/ci.yml Gatilhos: push e pull_request O que o pipeline faz:

  1. Valida o compose.yaml (docker compose config)
  2. Builda a imagem do serviço app
  3. Sobe a stack (docker compose up -d)
  4. Aguarda a app responder e testa criar uma tarefa via API (smoke test do CRUD)
  5. Derruba a stack (docker compose down -v, sempre, mesmo se algo falhar)

Print 8 — execução verde ✅ CI verde

7. Quebra proposital do CI

O que eu quebrei: troquei CMD ["node", "src/index.js"] por CMD ["node", "src/indexx.js"] no Dockerfile (arquivo que não existe). Erro que apareceu no log: Error: Cannot find module '/app/src/indexx.js' Como o CI reagiu: o job passou pelo build normalmente (o Docker não valida se o arquivo do CMD existe na hora de buildar), mas falhou no step "Aguardar a aplicação responder" — o container do app subia e morria na hora (MODULE_NOT_FOUND), então o curl nunca conseguia conectar em http://localhost:3000/items, e depois das 30 tentativas o step retornava exit 1. Como eu corrigi: voltei o CMD para o caminho certo (src/index.js), na mesma branch quebra-proposital, com um segundo commit.

Link do Pull Request: #1

Print 9 — execução vermelha ❌ + log do erro CI vermelho

8. Dificuldades e aprendizados

  • Permissão negada rodando como não-root: ao trocar pro usuário node no Dockerfile, o app quebrou com EACCES tentando criar /etc/todos (onde fica o banco SQLite). Entendi que rodar sem root é ótimo pra segurança, mas qualquer pasta que a aplicação precise escrever tem que já existir com o dono certo antes do USER trocar — resolvi criando a pasta e dando chown pro usuário node ainda como root, no próprio Dockerfile.
  • Confundi container avulso com Compose: no teste de volume, tentei docker rm -f todo e deu "no such container" — só depois percebi que o que estava rodando era a stack do Compose (meu-projeto-docker-app-1), não o container todo que eu tinha criado antes. Aprendi na prática que container avulso (docker run) e serviço do Compose são coisas diferentes, mesmo rodando a mesma imagem.
  • Dado "fantasma" no teste de volume: ao testar a persistência, apareceu uma tarefa que eu não tinha cadastrado — era sobra de um teste anterior no mesmo volume nomeado (todo-db). Voltou a mostrar que volume nomeado realmente não é apagado sozinho — se reaproveitar o mesmo nome, o dado antigo continua lá, então pra um teste limpo é preciso remover o volume explicitamente.
  • CI quebrou sem eu mexer no código: depois de só adicionar os prints (imagens) e dar push, o pipeline falhou no build da imagem — parecia não ter relação nenhuma com o que eu tinha mudado. Fui olhar o log e o problema era o sqlite3 (dependência nativa) tentando compilar do zero por falta do Python no estágio de build, algo que só acontece quando o binário pré-compilado não baixa. Foi a maior lição da atividade: o CI pega até os erros que "não deveriam" acontecer, e o log é sempre o primeiro lugar pra olhar antes de suspeitar do que você mudou por último.

9. Checklist de autoavaliação

  • Dockerfile multi-stage funcionando
  • .dockerignore presente
  • Container não roda como root
  • Volume nomeado + persistência demonstrada
  • Rede nomeada + banco não exposto ao host
  • compose.yaml sobe tudo com um comando
  • .env no .gitignore e .env.example versionado
  • CI verde
  • PR com CI vermelho documentado (#1, merged)
  • Todos os 9 prints no README

About

Containerização com Docker + Compose + CI/CD — Dockerfile multi-stage, volumes, redes e GitHub Actions. ITEAM Módulo 11

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages