DEV Community

Cover image for Como construí um pipeline CI/CD com GitHub Actions, Docker e Terraform (e o que aprendi no caminho)
Marcos Vilela
Marcos Vilela

Posted on

Como construí um pipeline CI/CD com GitHub Actions, Docker e Terraform (e o que aprendi no caminho)

Recentemente executei um projeto que me ajudou a revisitar conceitos importantes de CI/CD, containerização e infraestrutura como código.
Nesse post, vou compartilhar como foi a experiência, as tecnologias envolvidas, dificuldades encontradas e os aprendizados que levei comigo. Esse post é muito mais sobre o que aprendi do que ferramentas para utilizar, exercitando então a capacidade de projetar, desenhar e integrar ferramentas e soluções modernas e entender como e quando o momento de utiliza-los.

O Projeto

A ideia era simples: criar uma API em Python com Flask que retorna frases motivacionais aleatórias, conteinerizá-la com Docker e configurá-la para ser implantada automaticamente usando GitHub Actions e Terraform em um provedor de nuvem.

O resultado final foi um pipeline que:

  1. Constrói a imagem Docker da aplicação.
  2. Faz o deploy automaticamente em um provedor de nuvem.
  3. Provisiona a infraestrutura via Terraform.

Tudo isso sendo disparado sempre que um commit é feito na branch main.

Tecnologias Utilizadas

  • Python + Flask – para criar a API.
  • Docker – para empacotar a aplicação e garantir portabilidade.
  • GitHub Actions – para criar o pipeline CI/CD.
  • Terraform – para gerenciar a infraestrutura como código.
  • Cloud – como provedor de hospedagem e deploy.

Estrutura Básica da API

O código da aplicação ficou simples, nesse exemplo abaixo suficiente para demonstrar a estrutura:

# app/advice.py
from flask import Flask, jsonify
import random

app = Flask(__name__)

frases = [
    "Acredite em você!",
    "Você é capaz de mais do que imagina.",
    "Persistência leva ao sucesso.",
    "Cada erro é uma oportunidade de aprendizado."
]

@app.route("/")
def home():
    return jsonify({"message": "Bem-vindo à API de Frases Motivacionais!"})

@app.route("/frase")
def frase():
    return jsonify({"frase": random.choice(frases)})

@app.errorhandler(404)
def not_found(error):
    return jsonify({"error": "Rota não encontrada"}), 404

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=80)
Enter fullscreen mode Exit fullscreen mode

Dockerfile para Containerização

A imagem foi construída a partir de uma base Python e exposta na porta 80:

FROM python:3.10-slim

WORKDIR /app

COPY app/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app/ .

EXPOSE 80

CMD ["python", "advice.py"]
Enter fullscreen mode Exit fullscreen mode

Para testar localmente:

docker build -t api-advice .
docker run -p 80:80 api-advice
Enter fullscreen mode Exit fullscreen mode

Workflow do GitHub Actions

O workflow foi configurado para rodar na branch main e fluxos separados para executar build, teste e deploy:

name: CI Pipeline

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

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Build da imagem Docker
        run: docker build -t test-image:latest .

      - name: Rodar testes dentro do container
        run: |
          docker run --rm -d -p 80:80 --name test-container test-image:latest
          sleep 5
          curl -f http://localhost/ || exit 1
          curl -f http://localhost/frase || exit 1
          curl -s -o /dev/null -w "%{http_code}" http://localhost/inexistente | grep -q "404" && echo "404 OK" || exit 1
          docker stop test-container

      - name: Testes passaram ✅
        run: echo "Todos os testes passaram com sucesso!"
Enter fullscreen mode Exit fullscreen mode

Action para infra

name: Provisionar Infraestrutura Render

on:
  push:
    branches: [infra]

jobs:
  terraform:
    runs-on: ubuntu-latest
    environment: render

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.12.2

      - name: Terraform Init
        working-directory: infra
        run: terraform init

      - name: Terraform Plan
        working-directory: infra
        env:
          TF_VAR_cloud_api_key: ${{ secrets.SECRET_API_KEY }}
          TF_VAR_cloud_owner_id: ${{ secrets.SECRET_OWNER_ID }}
        run: terraform plan -out=tfplan

      - name: Terraform Apply
        working-directory: infra
        env:
          TF_VAR_cloud_api_key: ${{ secrets.SECRET_API_KEY }}
          TF_VAR_cloud_owner_id: ${{ secrets.SECRET_OWNER_ID }}
        run: terraform apply -auto-approve
Enter fullscreen mode Exit fullscreen mode

Código Terraform para o Cloud

No diretório infra/, configurei os recursos para o respectivo provedor:

terraform {
  required_providers {
    Cloud = {
      source  = "Seu-Provedor-Cloud-os/Cloud"
      version = "~> 0.2"
    }
  }
}

provider "Cloud" {
  api_key = var.Cloud_api_key
}

resource "Cloud_service" "api" {
  name    = "api-advice"
  type    = "web_service"
  plan    = "starter"
  env     = "docker"
  branch  = "main"
  repo    = "https://github.com/seu-usuario/seu-repo"
}
Enter fullscreen mode Exit fullscreen mode

Principais Aprendizados

1. Containerização com Docker

Revisitei a criação de uma imagem Docker para uma aplicação Flask, expondo a porta 80 e configurando corretamente o CMD para inicializar o servidor.
Também testei chamadas à API com curl para validar o funcionamento dentro do container.

2. Automação com GitHub Actions

Configurei um workflow que:

  • Faz build da imagem Docker.
  • Publica a aplicação automaticamente em um provedor de nuvem.
  • Provisiona infraestrutura com Terraform.

3. Infraestrutura como Código com Terraform

Reforcei conceitos de organização de arquivos (main.tf, variables.tf, outputs.tf, terraform.tfvars) e boas práticas no uso de variáveis e outputs.

4. Estrutura de Projeto

Manter a separação entre código da aplicação e código de infraestrutura facilitou bastante a manutenção.

Dificuldades Encontradas

  • Revisão do Terraform: fazia algum tempo que não trabalhava com Terraform, então precisei revisar a documentação oficial para relembrar a sintaxe correta de módulos e garantir compatibilidade com as versões atuais.
  • Aprendendo sobre o provedor: como não conhecia o provedor definido para esse projeto, levei um tempo extra para entender como ele realiza implantações automáticas a partir de commits na branch main.

Ferramentas de Apoio

Durante o desenvolvimento, utilizei algumas ferramentas de IA que ajudaram a acelerar a curva de aprendizado:

  • ChatGPT (OpenAI): para esclarecer dúvidas e sugerir soluções.
  • GitHub Copilot: para agilizar a escrita de código e arquivos de configuração.
  • Claude Sonnet 3.7 (Anthropic): para revisar e sugerir melhorias na organização do projeto.

Conclusão

Esse projeto foi mais do que criar uma API simples — foi um exercício prático de integração entre desenvolvimento, automação e infraestrutura.
Revisitei conceitos importantes e aprendi novos fluxos de trabalho com ferramentas modernas como o Render.

Se você está começando com CI/CD, recomendo fortemente criar um projeto do zero, integrando Docker, Terraform e um pipeline de deploy automatizado.
A experiência prática é insubstituível.

Separação dos Pipelines

Foram criados dois workflows separados:

  • Pipeline CI (ci.yml): responsável por buildar a imagem Docker e rodar testes automatizados simples (incluindo verificação de rotas e status HTTP). O deploy no Render é acionado automaticamente via integração do próprio Render com o GitHub assim que há um push na branch main.
  • Pipeline de Infraestrutura (infra.yml): dedicado ao provisionamento da infraestrutura via Terraform, disparado por pushes na branch infra.

Essa separação garante maior organização, facilita troubleshooting e permite evoluir cada parte do processo de forma independente.

Observação: O pipeline de infraestrutura foi configurado para provisionar recursos em um provedor de cloud. No projeto original, foi utilizado o Render, mas o fluxo pode ser adaptado para outros provedores conforme necessidade.

Top comments (0)