Dia 30: Projecto Final no Linux — Stack Web, DB e CI/CD

Chegámos ao último dia do curso Linux 30 Dias. Ao longo de 29 dias cobrimos desde a linha de comandos básica até à administração de sistemas, redes, segurança, automação, backups, contentores e orquestração. Hoje juntamos tudo num projecto final: uma stack completa com servidor web, base de dados, monitorização e CI/CD, implantada com Docker Compose e automatizada com GitHub Actions.

1. Introdução — Revisão dos 29 Dias

Antes de mergulhar no projecto final, vale a pena recapitular o que aprendemos ao longo deste curso de 30 dias. Cada dia construiu uma competência que hoje se combina num deploy completo.

Dias Tema Competências-chave
1–5 Fundamentos Shell, ficheiros, permissões, processos, pacotes
6–10 Administração systemd, utilizadores, rede, firewall, SSH
11–15 Armazenamento LVM, RAID, backups, NFS, quotas
16–20 Segurança e Rede DNS, SSL/TLS, SELinux, VPN, auditoria
21–25 Automação Shell scripts, cron, Ansible, Terraform, Git
26–30 Contentores e DevOps Docker, Compose, Kubernetes, monitorização, CI/CD

O projecto final vai usar especificamente competências dos dias 21–30: Docker, Docker Compose, Prometheus, Grafana e GitHub Actions. Mas o conhecimento dos dias anteriores — rede, systemd, SSH, segurança — é o que permite operar esta stack em produção de forma fiável.

✓ Objectivo do Dia 30: Implantar uma stack de produção com Nginx + PostgreSQL + Prometheus + Grafana, com pipeline CI/CD que testa e faz a implantação automaticamente a cada push.

2. Arquitectura da Stack

A stack final tem cinco componentes que comunicam entre si através de uma rede Docker dedicada. Cada componente tem uma responsabilidade clara:

Componente Imagem Função Porta
Nginx nginx:alpine Servidor web / reverse proxy 8080
App python:3.12-slim Aplicação Flask com healthcheck 5000
PostgreSQL postgres:16-alpine Base de dados relacional 5432
Prometheus prom/prometheus Recolha de métricas 9090
Grafana grafana/grafana Dashboards e alertas 3000

O fluxo de pedidos é o seguinte: o cliente liga ao Nginx (porta 8080), que faz proxy reverso para a aplicação Flask (porta 5000). A aplicação lê e escreve no PostgreSQL (porta 5432). O Prometheus faz scraping de métricas da aplicação a cada 15 segundos. O Grafana visualiza os dados recolhidos pelo Prometheus.

⚠ Nota: Em produção real, as portas do PostgreSQL e da aplicação não devem estar expostas ao hospedeiro. Apenas o Nginx (8080), Prometheus (9090) e Grafana (3000) precisam de acesso externo. As restantes comunicam apenas na rede interna Docker.

3. Docker Compose — Web + DB

O docker-compose.yml define todos os serviços, redes e volumes. Começamos pelos serviços de aplicação e base de dados.

A aplicação Flask expõe um endpoint /metrics no formato Prometheus, e um endpoint /health para verificar se está viva.

# docker-compose.yml
version: "3.9"

services:
  db:
    image: postgres:16-alpine
    container_name: stack_db
    environment:
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: \${DB_PASSWORD}
      POSTGRES_DB: appdb
    volumes:
      - pgdata:/var/lib/postgresql/data
    networks:
      - internal
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  app:
    build: ./app
    container_name: stack_app
    depends_on:
      db:
        condition: service_healthy
    environment:
      DATABASE_URL: postgresql://appuser:\${DB_PASSWORD}@db:5432/appdb
    networks:
      - internal
    expose:
      - "5000"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
      interval: 15s
      timeout: 5s
      retries: 3
    restart: unless-stopped

  web:
    image: nginx:alpine
    container_name: stack_web
    depends_on:
      - app
    ports:
      - "8080:80"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
    networks:
      - internal
    restart: unless-stopped

volumes:
  pgdata:

networks:
  internal:
    driver: bridge

A configuração do Nginx faz proxy reverso para a aplicação e bloqueia acessos directos à base de dados:

# nginx/default.conf
server {
    listen 80;
    server_name localhost;

    location / {
        proxy_pass http://app:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location /metrics {
        proxy_pass http://app:5000/metrics;
        allow 172.16.0.0/12;
        deny all;
    }
}

ℹ Dica: O bloco allow/deny no /metrics garante que só a rede interna Docker (172.16.0.0/12) consegue aceder às métricas, impedindo exposição pública de dados de monitorização.

4. Monitorização — Prometheus + Grafana

O monitorização é adicionado ao mesmo docker-compose.yml. O Prometheus recolhe métricas da aplicação via scraping HTTP, e o Grafana visualiza essas métricas em dashboards.

A configuração do Prometheus define os alvos de scraping (targets):

# prometheus/prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: "app"
    static_configs:
      - targets: ["app:5000"]
    metrics_path: /metrics

  - job_name: "prometheus"
    static_configs:
      - targets: ["localhost:9090"]

Adicionamos os serviços de monitorização ao Compose:

# Adicionar ao docker-compose.yml
  prometheus:
    image: prom/prometheus:latest
    container_name: stack_prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - promdata:/prometheus
    networks:
      - internal
    restart: unless-stopped

  grafana:
    image: grafana/grafana:latest
    container_name: stack_grafana
    depends_on:
      - prometheus
    ports:
      - "3000:3000"
    environment:
      GF_SECURITY_ADMIN_PASSWORD: \${GRAFANA_PASSWORD}
    volumes:
      - grafanadata:/var/lib/grafana
    networks:
      - internal
    restart: unless-stopped

volumes:
  pgdata:
  promdata:
  grafanadata:

Métricas essenciais a monitorizar nesta stack:

Métrica Componente Alerta sugerido
http_requests_total App Taxa de pedidos > 1000/s
http_request_duration_seconds App P99 > 2s
pg_up PostgreSQL Valor = 0 (DB em baixo)
container_memory_usage_bytes Docker > 80% do limite
up Prometheus Target down por > 1min

✓ Boa prática: Configurar alertas no Grafana para enviar notificações via Slack ou email quando uma métrica ultrapassa o limiar definido. Ver documentação oficial do Grafana Alerting.

5. CI/CD com GitHub Actions

O pipeline de CI/CD automatiza o teste e o deploy. A cada push para o ramo main, o GitHub Actions executa os testes da aplicação, constrói a imagem Docker e faz a implantação no servidor.

O workflow tem três jobs: test, build e deploy. Cada job só corre se o anterior passar.

# .github/workflows/ci-cd.yml
name: CI/CD Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  IMAGE_NAME: stack-app

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install -r app/requirements.txt pytest
      - run: cd app && python -m pytest tests/ -v

  build:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          username: \${{ secrets.DOCKER_USER }}
          password: \${{ secrets.DOCKER_PASS }}
      - uses: docker/build-push-action@v5
        with:
          context: ./app
          push: true
          tags: |
            \${{ env.IMAGE_NAME }}:latest
            \${{ env.IMAGE_NAME }}:\${{ github.sha }}

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1
        with:
          host: \${{ secrets.DEPLOY_HOST }}
          username: \${{ secrets.DEPLOY_USER }}
          key: \${{ secrets.DEPLOY_KEY }}
          script: |
            cd /opt/stack
            export DB_PASSWORD=\$(cat /opt/stack/.env | grep DB_PASSWORD | cut -d= -f2)
            export GRAFANA_PASSWORD=\$(cat /opt/stack/.env | grep GRAFANA_PASSWORD | cut -d= -f2)
            docker compose pull
            docker compose up -d --force-recreate
            docker image prune -f
Job Trigger Acção
test push + PR Executa pytest
build push main Constroi e publica imagem
deploy push main SSH + docker compose up

⚠ Segurança: Os secrets DEPLOY_KEY, DOCKER_PASS e DB_PASSWORD devem ser configurados em Settings > Secrets and variables > Actions no repositório GitHub. Nunca commitar credenciais no código. Ver GitHub Actions Secrets.

6. Implantação e Teste

Com os ficheiros prontos, o a implantação é simples. No servidor, clonamos o repositório, configuramos as variáveis de ambiente e arrancamos a stack.

# 1. Clonar o repositório no servidor
git clone https://github.com/utilizador/stack-final.git /opt/stack
cd /opt/stack

# 2. Criar ficheiro .env com credenciais
cat > .env << EOF
DB_PASSWORD=alterar123
GRAFANA_PASSWORD=alterar456
EOF

# 3. Arrancar a stack completa
docker compose up -d

# 4. Verificar se todos os containers estão a correr
docker compose ps

# 5. Ver logs em tempo real
docker compose logs -f --tail=50

Verificar cada serviço individualmente:

# Testar o servidor web (Nginx)
curl -s http://localhost:8080/ | head -5

# Verificar health da aplicação
curl -s http://localhost:8080/health
# Esperado: {"status":"ok"}

# Confirmar que o Prometheus recolhe métricas
curl -s http://localhost:9090/api/v1/targets | python3 -m json.tool | grep health
# Esperado: "health": "up"

# Aceder ao Grafana (browser)
# http://SERVIDOR:3000  (admin / GRAFANA_PASSWORD)

Comandos de gestão do dia a dia:

# Reiniciar um serviço específico
docker compose restart app

# Actualizar imagem após novo build
docker compose pull app && docker compose up -d app

# Ver consumo de recursos por container
docker stats --no-stream

# Backup da base de dados
docker exec stack_db pg_dump -U appuser appdb > backup_$(date +%F).sql

# Parar e remover todos os containers (mantém volumes)
docker compose down

# Parar e remover volumes (CUIDADO: apaga dados)
docker compose down -v

ℹ Teste de CI/CD: Para testar o pipeline sem esperar por um push, forçar manualmente o workflow em Actions > CI/CD Pipeline > Run workflow. Isto é útil para validar mudanças de configuração antes de as integrar no main.

7. Conclusão e Próximos Passos

Parabéns por chegar ao fim do curso Linux 30 Dias. Neste último dia juntámos Docker Compose, Prometheus, Grafana e GitHub Actions numa stack completa e pronta para produção. O resultado é um sistema onde cada commit no repositório desencadeia testes automáticos, build de imagem e deploy — exactamente o que uma equipa DevOps faz no dia a dia.

Resumo do que esta stack implementa:

  • Servidor web com Nginx como reverse proxy e SSL termination
  • Aplicação Flask com healthcheck e endpoint de métricas Prometheus
  • Base de dados PostgreSQL com volume persistente e healthcheck
  • Monitorização com Prometheus (recolha) e Grafana (dashboards + alertas)
  • CI/CD com GitHub Actions — test, build e implantação automáticos
  • Rede isolada — apenas portas necessárias expostas ao hospedeiro

Próximos passos para evoluir este projecto:

Melhoria Benefício
Migrar para Kubernetes auto-escala, rollouts e resiliência multi-nó
Adicionar certificados TLS HTTPS automático com Let's Encrypt / Certbot
Centralizar logs ELK stack ou Loki + Promtail
Configurar alertas Alertmanager + Slack/Telegram
Blue-green implantação Zero downtime em actualizações

✓ Conclusão: Em 30 dias passámos de ls e cd a uma stack completa de produção com CI/CD. O caminho de aprendizagem não para aqui — Kubernetes, service mesh, observabilidade avançada e segurança em contentores são os próximos degraus. Continua a praticar, a automatizar e a documentar.

Obrigado por acompanhar o curso Linux 30 Dias. Se tiveres dúvidas ou quiseres partilhar o teu projecto final, deixa um comentário abaixo. Boas implantações!