trgclub — Multi-module repo scaffold — Implementation Plan

TLDR: Criar o repo trgclub no padrão WeHive multi-module com backend (Rails) e frontend (Next.js), infraestrutura K8s + Terraform e CI/CD para staging e production em endpoints beta.*.

Branch: feat/multi-module-scaffold

Architecture: O repo agrupa trgclub-api e trgclub-web como módulos independentes em modules/backend e modules/frontend, seguindo o padrão idêntico ao nectar-charges. Cada módulo tem seu próprio .infra/ com K8s (base + overlays) e Terraform. O CI/CD usa reusable workflows de ibft-corp/pipelines.

Tech stack: Ruby 3.4.4 (backend), Node 20.18.0 (frontend), Kustomize, Terraform, GitHub Actions.

Global constraints

  • Portas TRG Club (código 05): API_PORT=4005, WEB_PORT=3005, DATABASE_PORT=5005
  • ECR registry: 616795656532.dkr.ecr.us-east-1.amazonaws.com
  • Namespace K8s: trgclub--staging e trgclub--production
  • Domínio: trgclub.com (beta endpoints, não definitivos)
  • Ward path: trgclub.infra
  • Terraform state bucket: wehive-terraform-states

Task 1: Estrutura raiz do repo

Files: - Create: Makefile - Create: compose.yml - Create: .env.example - Create: .gitignore - Create: .tool-versions - Create: .github/PULL_REQUEST_TEMPLATE.md - Create: .project/docker/compose/services.yml - Create: .project/docs/modules.md - Create: tasks/registry/create.sh

  • [ ] Step 1: Criar Makefile raiz

```makefile # Makefile include $(COMMONS_DIR)/make/main.makefile

frontend.%: @$(MAKE) -C modules/frontend $*

backend.%: @$(MAKE) -C modules/backend $*

run.test: @$(MAKE) -j2 backend.run.test frontend.run.test

modules.image.push: @$(MAKE) backend.image.dev.push frontend.image.dev.push

registry.create: @sh tasks/registry/create.sh ```

  • [ ] Step 2: Criar compose.yml raiz

```yaml # compose.yml name: trgclub

include: - path: - ${COMMONS_DIR}/docker/compose/shared/database.yml - .project/docker/compose/services.yml ```

  • [ ] Step 3: Criar .project/docker/compose/services.yml

```yaml # .project/docker/compose/services.yml include: - path: - ${COMMONS_DIR}/docker/compose/stacks/ruby.yml - ${COMMONS_DIR}/docker/compose/stacks/node.yml

x-backend-base: &backend-base extends: file: ${COMMONS_DIR}/docker/compose/stacks/ruby.yml service: x-stack-base build: context: ${PROJECT_DIR}/modules/backend volumes: - ${PROJECT_DIR}/modules/backend:/source

x-frontend-base: &frontend-base extends: file: ${COMMONS_DIR}/docker/compose/stacks/node.yml service: x-stack-base build: context: ${PROJECT_DIR}/modules/frontend volumes: - ${PROJECT_DIR}/modules/frontend:/source

services: backend-api: «: *backend-base environment: PORT: ${API_PORT} ports: - “${API_PORT}:${API_PORT}” depends_on: database: condition: service_healthy

backend-worker: «: *backend-base environment: RUNTIME_MODE: worker depends_on: database: condition: service_healthy

frontend-web: «: *frontend-base ports: - “${WEB_PORT}:${WEB_PORT}” ```

  • [ ] Step 4: Criar .env.example

```bash # .env.example # ───────────────────────────────────────────── # Dev mode # ───────────────────────────────────────────── DEV_MODE=container DEV_DEPENDENCIES=start DEV_ALWAYS_BUILD_CONTAINERS=false

─────────────────────────────────────────────

# Stack versions # ───────────────────────────────────────────── NODE_VERSION=20 RUBY_VERSION=3.4.4

─────────────────────────────────────────────

# Frontend (Next.js dev server) # ───────────────────────────────────────────── WEB_PORT=3005

NEXT_PUBLIC_API_URL=http://localhost:4005

─────────────────────────────────────────────

# Backend (Rails API) # ───────────────────────────────────────────── APP_NAME=trgclub-api CONTAINER_REGISTRY=616795656532.dkr.ecr.us-east-1.amazonaws.com/wehive API_PORT=4005

CORS_ALLOWED_ORIGINS=http://localhost:3005

JWT_SECRET=changeme JWT_ACCESS_TTL=604800

DATABASE_URL_STAGING= DATABASE_URL_PRODUCTION=

─────────────────────────────────────────────

# Banco de dados (PostgreSQL) # ───────────────────────────────────────────── DATABASE_HOST=database DATABASE_PORT=5005 DATABASE_NAME=trgclub_api_development DATABASE_USERNAME=postgres DATABASE_PASSWORD=postgres DATABASE_POOL=5

─────────────────────────────────────────────

# Datadog (staging e production apenas) # ───────────────────────────────────────────── DD_API_KEY= DD_SITE=us5.datadoghq.com DD_ENV=staging DD_SERVICE=trgclub-api DD_VERSION= ```

  • [ ] Step 5: Criar .gitignore

gitignore node_modules/ dist/ dist-ssr/ *.local .env .commons .DS_Store .claude/settings.local.json .codex/settings.local.json .ward/.key modules/**/.claude/ modules/**/.codex/ modules/**/.project/ .infra/build-args modules/**/.infra/terraform/.terraform/ modules/**/.infra/terraform/.terraform.lock.hcl modules/**/.infra/terraform/tfplan modules/**/.infra/terraform/*.tfstate modules/**/.infra/terraform/*.tfstate.backup .claude/* !.claude/ !.claude/skills !.claude/commands .codex/* !.codex/ !.codex/skills !.codex/commands

  • [ ] Step 6: Criar .tool-versions

nodejs 20.18.0 ruby 3.4.4

  • [ ] Step 7: Criar .github/PULL_REQUEST_TEMPLATE.md

```markdown ## TLDR

CONTEXT

CHANGES

- - -

HOW TO TEST

1. 2. 3.


CHECKLIST

  • [ ] Código testado em staging
  • [ ] Testes automáticos criados/atualizados para as mudanças
  • [ ] PRs pequenos e atômicos (um contexto: feature/bug/regra de negócio)
  • [ ] Commits atômicos com mensagens claras (max 60 chars)
  • [ ] Código estável e não quebra funcionalidades existentes ```

  • [ ] Step 8: Criar tasks/registry/create.sh

```bash #!/bin/sh set -e

aws ecr create-repository –repository-name trgclub-backend –region us-east-1 –profile wehive-ecr aws ecr create-repository –repository-name trgclub-frontend –region us-east-1 –profile wehive-ecr ```

bash chmod +x tasks/registry/create.sh

  • [ ] Step 9: Criar .project/docs/modules.md

Copiar o conteúdo de nectar-charges/.project/docs/modules.md substituindo referências ao projeto.

  • [ ] Step 10: Commit

bash git add Makefile compose.yml .env.example .gitignore .tool-versions \ .github/PULL_REQUEST_TEMPLATE.md \ .project/docker/compose/services.yml \ .project/docs/modules.md \ tasks/registry/create.sh git commit -m "feat: root repo structure"


Task 2: Módulo backend — scaffold e conteúdo

Files: - Create: modules/backend/Makefile - Create: modules/backend/.module/make/main.mk - Create: modules/backend/.infra/build-args - Move: conteúdo de trgclub-api/ → modules/backend/

  • [ ] Step 1: Criar estrutura do módulo backend

bash mkdir -p modules/backend/.module/make mkdir -p modules/backend/.infra

  • [ ] Step 2: Criar modules/backend/Makefile

makefile include $(COMMONS_DIR)/make/main.makefile include .module/make/main.mk

  • [ ] Step 3: Criar modules/backend/.module/make/main.mk

makefile APP_SERVICE = backend-api MODULE_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))/../..) export APP_SERVICE export MODULE_DIR

  • [ ] Step 4: Criar modules/backend/.infra/build-args

DOCKERFILE=${COMMONS_DIR}/docker/Dockerfile STACK=ruby STACK_VERSION=3.4.4 DISTRO=alpine DEPS_PATH=/usr/local/bundle LOCK_FILES=Gemfile Gemfile.lock .ruby-version

  • [ ] Step 5: Copiar conteúdo do trgclub-api

bash # Copiar todo o conteúdo de trgclub-api para modules/backend # excluindo .git, node_modules e arquivos temporários rsync -av --exclude='.git' --exclude='node_modules' --exclude='tmp/cache' \ --exclude='log/*.log' --exclude='coverage' \ ../trgclub-api/ modules/backend/

  • [ ] Step 6: Commit

bash git add modules/backend/ git commit -m "feat: backend module scaffold with trgclub-api"


Task 3: Módulo frontend — scaffold e conteúdo

Files: - Create: modules/frontend/Makefile - Create: modules/frontend/.module/make/main.mk - Create: modules/frontend/.infra/build-args - Move: conteúdo de trgclub-web/ → modules/frontend/

  • [ ] Step 1: Criar estrutura do módulo frontend

bash mkdir -p modules/frontend/.module/make mkdir -p modules/frontend/.infra

  • [ ] Step 2: Criar modules/frontend/Makefile

makefile include $(COMMONS_DIR)/make/main.makefile include .module/make/main.mk

  • [ ] Step 3: Criar modules/frontend/.module/make/main.mk

makefile APP_SERVICE = frontend-web MODULE_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))/../..) export APP_SERVICE export MODULE_DIR

  • [ ] Step 4: Criar modules/frontend/.infra/build-args

DOCKERFILE=${COMMONS_DIR}/docker/Dockerfile STACK=node STACK_VERSION=20.18.0 DISTRO=alpine LOCK_FILES=package-lock.json

  • [ ] Step 5: Copiar conteúdo do trgclub-web

bash rsync -av --exclude='.git' --exclude='node_modules' --exclude='.next' \ --exclude='coverage' --exclude='cypress/videos' --exclude='cypress/screenshots' \ ../trgclub-web/ modules/frontend/

  • [ ] Step 6: Commit

bash git add modules/frontend/ git commit -m "feat: frontend module scaffold with trgclub-web"


Task 4: K8s backend — base

Files: - Create: modules/backend/.infra/k8s/base/kustomization.yaml - Create: modules/backend/.infra/k8s/base/specs/api-deployment.yaml - Create: modules/backend/.infra/k8s/base/specs/api-service.yaml - Create: modules/backend/.infra/k8s/base/specs/ingress.yaml - Create: modules/backend/.infra/k8s/base/specs/migrate-job.yaml - Create: modules/backend/.infra/k8s/base/specs/worker-deployment.yaml

  • [ ] Step 1: Criar diretórios

bash mkdir -p modules/backend/.infra/k8s/base/specs

  • [ ] Step 2: Criar kustomization.yaml

```yaml # modules/backend/.infra/k8s/base/kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization

resources: - specs/api-deployment.yaml - specs/api-service.yaml - specs/ingress.yaml ```

  • [ ] Step 3: Criar api-deployment.yaml

yaml # modules/backend/.infra/k8s/base/specs/api-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: backend-api labels: app: trgclub module: backend component: api spec: replicas: 1 selector: matchLabels: app: trgclub module: backend component: api strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 template: metadata: labels: app: trgclub module: backend component: api spec: terminationGracePeriodSeconds: 30 containers: - name: api image: 616795656532.dkr.ecr.us-east-1.amazonaws.com/trgclub-backend:latest ports: - containerPort: 4005 env: - name: RAILS_ENV value: "production" - name: RAILS_ALLOWED_HOSTS value: "placeholder.trgclub.com" envFrom: - secretRef: name: trgclub-secrets resources: requests: cpu: 100m memory: 256Mi limits: cpu: 500m memory: 512Mi readinessProbe: httpGet: path: /health port: 4005 initialDelaySeconds: 10 periodSeconds: 10

  • [ ] Step 4: Criar api-service.yaml

yaml # modules/backend/.infra/k8s/base/specs/api-service.yaml apiVersion: v1 kind: Service metadata: name: backend-api labels: app: trgclub module: backend component: api spec: selector: app: trgclub module: backend component: api ports: - port: 80 targetPort: 4005

  • [ ] Step 5: Criar ingress.yaml

yaml # modules/backend/.infra/k8s/base/specs/ingress.yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: backend-api labels: app: trgclub module: backend annotations: kubernetes.io/ingress.class: nginx cert-manager.io/cluster-issuer: letsencrypt-prod nginx.ingress.kubernetes.io/ssl-redirect: "true" nginx.ingress.kubernetes.io/proxy-read-timeout: "90" nginx.ingress.kubernetes.io/proxy-send-timeout: "90" spec: tls: - hosts: - placeholder.trgclub.com secretName: trgclub-api-tls rules: - host: placeholder.trgclub.com http: paths: - path: / pathType: Prefix backend: service: name: backend-api port: number: 80

  • [ ] Step 6: Criar migrate-job.yaml

yaml # modules/backend/.infra/k8s/base/specs/migrate-job.yaml apiVersion: batch/v1 kind: Job metadata: name: migrate spec: ttlSecondsAfterFinished: 120 template: spec: restartPolicy: Never containers: - name: migrate image: MIGRATE_IMAGE command: ["entrypoint"] args: ["run/migrate"] env: - name: RAILS_ENV value: "MIGRATE_ENV" envFrom: - secretRef: name: trgclub-secrets resources: requests: cpu: 50m memory: 64Mi limits: cpu: 500m memory: 512Mi

  • [ ] Step 7: Criar worker-deployment.yaml

yaml # modules/backend/.infra/k8s/base/specs/worker-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: backend-worker labels: app: trgclub module: backend component: worker spec: replicas: 1 selector: matchLabels: app: trgclub module: backend component: worker strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 template: metadata: labels: app: trgclub module: backend component: worker spec: terminationGracePeriodSeconds: 60 containers: - name: worker image: 616795656532.dkr.ecr.us-east-1.amazonaws.com/trgclub-backend:latest env: - name: RAILS_ENV value: "production" - name: RUNTIME_MODE value: worker envFrom: - secretRef: name: trgclub-secrets resources: requests: cpu: 100m memory: 256Mi limits: cpu: 500m memory: 512Mi

  • [ ] Step 8: Verificar kustomize

bash kubectl kustomize modules/backend/.infra/k8s/base

Expected: manifests renderizados sem erro.

  • [ ] Step 9: Commit

bash git add modules/backend/.infra/k8s/base/ git commit -m "feat: backend k8s base manifests"


Task 5: K8s backend — overlays staging e production

Files: - Create: modules/backend/.infra/k8s/overlays/staging/kustomization.yaml - Create: modules/backend/.infra/k8s/overlays/staging/namespace.yaml - Create: modules/backend/.infra/k8s/overlays/staging/patches/deployment.yaml - Create: modules/backend/.infra/k8s/overlays/staging/patches/ingress.yaml - Create: modules/backend/.infra/k8s/overlays/production/kustomization.yaml - Create: modules/backend/.infra/k8s/overlays/production/namespace.yaml - Create: modules/backend/.infra/k8s/overlays/production/patches/deployment.yaml - Create: modules/backend/.infra/k8s/overlays/production/patches/ingress.yaml

  • [ ] Step 1: Criar diretórios

bash mkdir -p modules/backend/.infra/k8s/overlays/staging/patches mkdir -p modules/backend/.infra/k8s/overlays/production/patches

  • [ ] Step 2: Staging — kustomization.yaml

```yaml # modules/backend/.infra/k8s/overlays/staging/kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization

namespace: trgclub–staging

resources: - ../../base - namespace.yaml

images: - name: 616795656532.dkr.ecr.us-east-1.amazonaws.com/trgclub-backend newTag: staging

patches: - path: patches/ingress.yaml - path: patches/deployment.yaml ```

  • [ ] Step 3: Staging — namespace.yaml

yaml # modules/backend/.infra/k8s/overlays/staging/namespace.yaml apiVersion: v1 kind: Namespace metadata: name: trgclub--staging

  • [ ] Step 4: Staging — patches/deployment.yaml

yaml # modules/backend/.infra/k8s/overlays/staging/patches/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: backend-api spec: template: spec: containers: - name: api env: - name: RAILS_ENV value: "staging" - name: RAILS_ALLOWED_HOSTS value: "beta.staging.api.trgclub.com"

  • [ ] Step 5: Staging — patches/ingress.yaml

yaml # modules/backend/.infra/k8s/overlays/staging/patches/ingress.yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: backend-api spec: tls: - hosts: - beta.staging.api.trgclub.com secretName: trgclub-api-tls rules: - host: beta.staging.api.trgclub.com http: paths: - path: / pathType: Prefix backend: service: name: backend-api port: number: 80

  • [ ] Step 6: Production — kustomization.yaml

```yaml # modules/backend/.infra/k8s/overlays/production/kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization

namespace: trgclub–production

resources: - ../../base - namespace.yaml

images: - name: 616795656532.dkr.ecr.us-east-1.amazonaws.com/trgclub-backend newTag: production

patches: - path: patches/ingress.yaml - path: patches/deployment.yaml ```

  • [ ] Step 7: Production — namespace.yaml

yaml # modules/backend/.infra/k8s/overlays/production/namespace.yaml apiVersion: v1 kind: Namespace metadata: name: trgclub--production

  • [ ] Step 8: Production — patches/deployment.yaml

yaml # modules/backend/.infra/k8s/overlays/production/patches/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: backend-api spec: template: spec: containers: - name: api env: - name: RAILS_ENV value: "production" - name: RAILS_ALLOWED_HOSTS value: "beta.api.trgclub.com"

  • [ ] Step 9: Production — patches/ingress.yaml

yaml # modules/backend/.infra/k8s/overlays/production/patches/ingress.yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: backend-api spec: tls: - hosts: - beta.api.trgclub.com secretName: trgclub-api-tls rules: - host: beta.api.trgclub.com http: paths: - path: / pathType: Prefix backend: service: name: backend-api port: number: 80

  • [ ] Step 10: Verificar overlays

bash kubectl kustomize modules/backend/.infra/k8s/overlays/staging kubectl kustomize modules/backend/.infra/k8s/overlays/production

Expected: manifests renderizados com namespaces e hosts corretos.

  • [ ] Step 11: Commit

bash git add modules/backend/.infra/k8s/overlays/ git commit -m "feat: backend k8s overlays staging and production"


Task 6: K8s frontend — base e overlays

Files: - Create: modules/frontend/.infra/k8s/base/kustomization.yaml - Create: modules/frontend/.infra/k8s/base/specs/rbac.yaml - Create: modules/frontend/.infra/k8s/base/specs/web-deployment.yaml - Create: modules/frontend/.infra/k8s/base/specs/web-service.yaml - Create: modules/frontend/.infra/k8s/base/specs/ingress.yaml - Create: overlays staging e production (mesma estrutura do backend)

  • [ ] Step 1: Criar diretórios

bash mkdir -p modules/frontend/.infra/k8s/base/specs mkdir -p modules/frontend/.infra/k8s/overlays/staging/patches mkdir -p modules/frontend/.infra/k8s/overlays/production/patches

  • [ ] Step 2: Criar base/kustomization.yaml

```yaml # modules/frontend/.infra/k8s/base/kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization

resources: - specs/rbac.yaml - specs/web-deployment.yaml - specs/web-service.yaml - specs/ingress.yaml ```

  • [ ] Step 3: Criar base/specs/rbac.yaml

yaml # modules/frontend/.infra/k8s/base/specs/rbac.yaml apiVersion: v1 kind: ServiceAccount metadata: name: frontend-web --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: frontend-web rules: - apiGroups: ["apps"] resources: ["deployments"] verbs: ["get", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: frontend-web roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: frontend-web subjects: - kind: ServiceAccount name: frontend-web

  • [ ] Step 4: Criar base/specs/web-deployment.yaml

yaml # modules/frontend/.infra/k8s/base/specs/web-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: frontend-web labels: app: trgclub module: frontend component: web spec: replicas: 1 selector: matchLabels: app: trgclub module: frontend component: web strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 template: metadata: labels: app: trgclub module: frontend component: web spec: serviceAccountName: frontend-web terminationGracePeriodSeconds: 30 initContainers: - name: wait-backend image: bitnami/kubectl:latest command: - kubectl - rollout - status - deployment/backend-api - --timeout=5m containers: - name: web image: 616795656532.dkr.ecr.us-east-1.amazonaws.com/trgclub-frontend:latest env: - name: RUNTIME_MODE value: "web" - name: WEB_PORT value: "3005" - name: VITE_ALLOWED_HOSTS value: "placeholder.trgclub.com" ports: - containerPort: 3005 resources: requests: cpu: 50m memory: 64Mi limits: cpu: 200m memory: 256Mi

  • [ ] Step 5: Criar base/specs/web-service.yaml

yaml # modules/frontend/.infra/k8s/base/specs/web-service.yaml apiVersion: v1 kind: Service metadata: name: frontend-web labels: app: trgclub module: frontend component: web spec: selector: app: trgclub module: frontend component: web ports: - port: 80 targetPort: 3005

  • [ ] Step 6: Criar base/specs/ingress.yaml

yaml # modules/frontend/.infra/k8s/base/specs/ingress.yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: frontend-web labels: app: trgclub module: frontend annotations: kubernetes.io/ingress.class: nginx cert-manager.io/cluster-issuer: letsencrypt-prod nginx.ingress.kubernetes.io/ssl-redirect: "true" spec: tls: - hosts: - placeholder.trgclub.com secretName: trgclub-web-tls rules: - host: placeholder.trgclub.com http: paths: - path: / pathType: Prefix backend: service: name: frontend-web port: number: 80

  • [ ] Step 7: Staging — overlays

```yaml # modules/frontend/.infra/k8s/overlays/staging/kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization

namespace: trgclub–staging

resources: - ../../base - namespace.yaml

images: - name: 616795656532.dkr.ecr.us-east-1.amazonaws.com/trgclub-frontend newTag: staging

patches: - path: patches/ingress.yaml - path: patches/deployment.yaml ```

yaml # modules/frontend/.infra/k8s/overlays/staging/namespace.yaml apiVersion: v1 kind: Namespace metadata: name: trgclub--staging

yaml # modules/frontend/.infra/k8s/overlays/staging/patches/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: frontend-web spec: template: spec: containers: - name: web env: - name: VITE_ALLOWED_HOSTS value: "beta.staging.trgclub.com"

yaml # modules/frontend/.infra/k8s/overlays/staging/patches/ingress.yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: frontend-web spec: tls: - hosts: - beta.staging.trgclub.com secretName: trgclub-web-tls rules: - host: beta.staging.trgclub.com http: paths: - path: / pathType: Prefix backend: service: name: frontend-web port: number: 80

  • [ ] Step 8: Production — overlays

```yaml # modules/frontend/.infra/k8s/overlays/production/kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization

namespace: trgclub–production

resources: - ../../base - namespace.yaml

images: - name: 616795656532.dkr.ecr.us-east-1.amazonaws.com/trgclub-frontend newTag: production

patches: - path: patches/ingress.yaml - path: patches/deployment.yaml ```

yaml # modules/frontend/.infra/k8s/overlays/production/namespace.yaml apiVersion: v1 kind: Namespace metadata: name: trgclub--production

yaml # modules/frontend/.infra/k8s/overlays/production/patches/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: frontend-web spec: template: spec: containers: - name: web env: - name: VITE_ALLOWED_HOSTS value: "beta.trgclub.com"

yaml # modules/frontend/.infra/k8s/overlays/production/patches/ingress.yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: frontend-web spec: tls: - hosts: - beta.trgclub.com secretName: trgclub-web-tls rules: - host: beta.trgclub.com http: paths: - path: / pathType: Prefix backend: service: name: frontend-web port: number: 80

  • [ ] Step 9: Verificar overlays

bash kubectl kustomize modules/frontend/.infra/k8s/overlays/staging kubectl kustomize modules/frontend/.infra/k8s/overlays/production

Expected: manifests renderizados sem erro.

  • [ ] Step 10: Commit

bash git add modules/frontend/.infra/k8s/ git commit -m "feat: frontend k8s base and overlays"


Task 7: Terraform backend

Files: - Create: modules/backend/.infra/terraform/backend.tf - Create: modules/backend/.infra/terraform/data.tf - Create: modules/backend/.infra/terraform/database.tf - Create: modules/backend/.infra/terraform/dns.tf - Create: modules/backend/.infra/terraform/main.tf - Create: modules/backend/.infra/terraform/namespace.tf - Create: modules/backend/.infra/terraform/secrets.tf - Create: modules/backend/.infra/terraform/variables.tf - Create: modules/backend/.infra/terraform/versions.tf - Create: modules/backend/.infra/terraform/.tool-versions

  • [ ] Step 1: Criar diretório

bash mkdir -p modules/backend/.infra/terraform

  • [ ] Step 2: Criar .tool-versions

terraform 1.10.3

  • [ ] Step 3: Criar backend.tf

hcl # modules/backend/.infra/terraform/backend.tf terraform { backend "s3" { bucket = "wehive-terraform-states" key = "stacks/trgclub/backend/terraform.tfstate" region = "us-east-1" encrypt = true } }

  • [ ] Step 4: Criar versions.tf

```hcl # modules/backend/.infra/terraform/versions.tf terraform { required_version = “>= 1.5.0”

required_providers { aws = { source = “hashicorp/aws” version = “~> 5.0” } kubernetes = { source = “hashicorp/kubernetes” version = “~> 2.23” } cloudflare = { source = “cloudflare/cloudflare” version = “~> 4.0” } random = { source = “hashicorp/random” version = “~> 3.0” } } } ```

  • [ ] Step 5: Criar data.tf

```hcl # modules/backend/.infra/terraform/data.tf provider “aws” { region = var.aws_region access_key = var.aws_terraform_access_key_id secret_key = var.aws_terraform_secret_access_key

assume_role { role_arn = “arn:aws:iam::${var.aws_shared_account_id}:role/TerraformRole” } }

data “aws_eks_cluster” “shared” { name = “shared-kubernetes” }

data “aws_eks_cluster_auth” “shared” { name = “shared-kubernetes” }

data “aws_subnets” “shared” { filter { name = “vpc-id” values = [var.aws_shared_vpc_id] } }

provider “kubernetes” { host = data.aws_eks_cluster.shared.endpoint cluster_ca_certificate = base64decode(data.aws_eks_cluster.shared.certificate_authority[0].data) token = data.aws_eks_cluster_auth.shared.token }

provider “cloudflare” { api_token = var.cloudflare_api_token } ```

  • [ ] Step 6: Criar main.tf

```hcl # modules/backend/.infra/terraform/main.tf locals { environment = terraform.workspace

namespace = “trgclub–${local.environment}”

domain = “trgclub.com”

# staging: beta.staging.api.trgclub.com # production: beta.api.trgclub.com app_subdomain = local.environment == “production” ? “beta.api” : “beta.staging.api” app_fqdn = “${local.app_subdomain}.${local.domain}”

common_labels = { app = “trgclub” module = “backend” environment = local.environment managed-by = “terraform” } } ```

  • [ ] Step 7: Criar namespace.tf

```hcl # modules/backend/.infra/terraform/namespace.tf import { to = kubernetes_namespace.trgclub id = local.namespace }

resource “kubernetes_namespace” “trgclub” { metadata { name = local.namespace labels = local.common_labels } } ```

  • [ ] Step 8: Criar database.tf

```hcl # modules/backend/.infra/terraform/database.tf resource “random_password” “db” { length = 32 special = false }

resource “aws_security_group” “aurora” { name = “trgclub-aurora-sg-${local.environment}” description = “Aurora access from shared VPC” vpc_id = var.aws_shared_vpc_id

ingress { from_port = 5432 to_port = 5432 protocol = “tcp” cidr_blocks = [var.aws_shared_vpc_cidr] }

egress { from_port = 0 to_port = 0 protocol = “-1” cidr_blocks = [“0.0.0.0/0”] }

tags = { Name = “trgclub-aurora-sg-${local.environment}” Environment = local.environment ManagedBy = “terraform” } }

resource “aws_db_subnet_group” “aurora” { name = “trgclub-aurora-subnets-${local.environment}” subnet_ids = data.aws_subnets.shared.ids

tags = { Name = “trgclub-aurora-subnets-${local.environment}” Environment = local.environment ManagedBy = “terraform” } }

resource “aws_rds_cluster” “trgclub” { cluster_identifier = “trgclub-cluster-${local.environment}” engine = “aurora-postgresql” engine_mode = “provisioned” engine_version = “17.4” database_name = “trgclub” master_username = “trgclub” master_password = random_password.db.result db_subnet_group_name = aws_db_subnet_group.aurora.name vpc_security_group_ids = [aws_security_group.aurora.id] skip_final_snapshot = true deletion_protection = false

serverlessv2_scaling_configuration { min_capacity = 0 max_capacity = 2 seconds_until_auto_pause = 300 }

tags = { Name = “trgclub-cluster-${local.environment}” Environment = local.environment ManagedBy = “terraform” } }

resource “aws_rds_cluster_instance” “writer” { identifier = “trgclub-cluster-writer-${local.environment}-1” cluster_identifier = aws_rds_cluster.trgclub.id instance_class = “db.serverless” engine = aws_rds_cluster.trgclub.engine engine_version = aws_rds_cluster.trgclub.engine_version db_subnet_group_name = aws_db_subnet_group.aurora.name publicly_accessible = false

tags = { Name = “trgclub-cluster-writer-${local.environment}-1” Environment = local.environment ManagedBy = “terraform” } } ```

  • [ ] Step 9: Criar dns.tf

```hcl # modules/backend/.infra/terraform/dns.tf resource “cloudflare_record” “trgclub_api” { zone_id = var.cloudflare_zone_id name = local.app_subdomain type = “CNAME” content = var.ingress_hostname ttl = 1 proxied = false

comment = “TRG Club API - ${local.environment} - Managed by Terraform” } ```

  • [ ] Step 10: Criar secrets.tf

```hcl # modules/backend/.infra/terraform/secrets.tf resource “kubernetes_secret” “trgclub” { metadata { name = “trgclub-secrets” namespace = kubernetes_namespace.trgclub.metadata[0].name labels = local.common_labels }

data = { RAILS_MASTER_KEY = var.rails_master_key

DATABASE_URL_READONLY   = "postgresql://${aws_rds_cluster.trgclub.master_username}:${aws_rds_cluster.trgclub.master_password}@${aws_rds_cluster.trgclub.reader_endpoint}:${aws_rds_cluster.trgclub.port}/${aws_rds_cluster.trgclub.database_name}?sslmode=require"
DATABASE_URL_FULLACCESS = "postgresql://${aws_rds_cluster.trgclub.master_username}:${aws_rds_cluster.trgclub.master_password}@${aws_rds_cluster.trgclub.endpoint}:${aws_rds_cluster.trgclub.port}/${aws_rds_cluster.trgclub.database_name}?sslmode=require"   }

type = “Opaque” } ```

  • [ ] Step 11: Criar variables.tf

```hcl # modules/backend/.infra/terraform/variables.tf variable “cloudflare_api_token” { type = string sensitive = true }

variable “cloudflare_zone_id” { type = string }

variable “rails_master_key” { type = string sensitive = true }

variable “aws_terraform_access_key_id” { type = string sensitive = true }

variable “aws_terraform_secret_access_key” { type = string sensitive = true }

variable “aws_shared_account_id” { type = string }

variable “aws_shared_vpc_id” { type = string description = “VPC ID of the shared AWS account” }

variable “aws_shared_vpc_cidr” { type = string description = “CIDR block of the shared VPC” }

variable “aws_region” { type = string default = “us-east-1” }

variable “ingress_hostname” { type = string description = “ALB hostname for the EKS ingress” } ```

  • [ ] Step 12: Commit

bash git add modules/backend/.infra/terraform/ git commit -m "feat: backend terraform"


Task 8: Terraform frontend

Files: - Create: modules/frontend/.infra/terraform/backend.tf - Create: modules/frontend/.infra/terraform/data.tf - Create: modules/frontend/.infra/terraform/dns.tf - Create: modules/frontend/.infra/terraform/main.tf - Create: modules/frontend/.infra/terraform/variables.tf - Create: modules/frontend/.infra/terraform/versions.tf - Create: modules/frontend/.infra/terraform/.tool-versions

  • [ ] Step 1: Criar diretório

bash mkdir -p modules/frontend/.infra/terraform

  • [ ] Step 2: Criar .tool-versions

terraform 1.10.3

  • [ ] Step 3: Criar backend.tf

hcl # modules/frontend/.infra/terraform/backend.tf terraform { backend "s3" { bucket = "wehive-terraform-states" key = "stacks/trgclub/frontend/terraform.tfstate" region = "us-east-1" encrypt = true } }

  • [ ] Step 4: Criar versions.tf

```hcl # modules/frontend/.infra/terraform/versions.tf terraform { required_version = “>= 1.5.0”

required_providers { aws = { source = “hashicorp/aws” version = “~> 5.0” } kubernetes = { source = “hashicorp/kubernetes” version = “~> 2.23” } cloudflare = { source = “cloudflare/cloudflare” version = “~> 4.0” } } } ```

  • [ ] Step 5: Criar data.tf

```hcl # modules/frontend/.infra/terraform/data.tf provider “aws” { region = var.aws_region access_key = var.aws_terraform_access_key_id secret_key = var.aws_terraform_secret_access_key

assume_role { role_arn = “arn:aws:iam::${var.aws_shared_account_id}:role/TerraformRole” } }

data “aws_eks_cluster” “shared” { name = “shared-kubernetes” }

data “aws_eks_cluster_auth” “shared” { name = “shared-kubernetes” }

provider “kubernetes” { host = data.aws_eks_cluster.shared.endpoint cluster_ca_certificate = base64decode(data.aws_eks_cluster.shared.certificate_authority[0].data) token = data.aws_eks_cluster_auth.shared.token }

provider “cloudflare” { api_token = var.cloudflare_api_token } ```

  • [ ] Step 6: Criar main.tf

```hcl # modules/frontend/.infra/terraform/main.tf locals { environment = terraform.workspace

namespace = “trgclub–${local.environment}”

domain = “trgclub.com”

# staging: beta.staging.trgclub.com # production: beta.trgclub.com app_subdomain = local.environment == “production” ? “beta” : “beta.staging” app_fqdn = “${local.app_subdomain}.${local.domain}”

common_labels = { app = “trgclub” module = “frontend” environment = local.environment managed-by = “terraform” } } ```

  • [ ] Step 7: Criar dns.tf

```hcl # modules/frontend/.infra/terraform/dns.tf resource “cloudflare_record” “trgclub_web” { zone_id = var.cloudflare_zone_id name = local.app_subdomain type = “CNAME” content = var.ingress_hostname ttl = 1 proxied = false

comment = “TRG Club Web - ${local.environment} - Managed by Terraform” } ```

  • [ ] Step 8: Criar variables.tf

```hcl # modules/frontend/.infra/terraform/variables.tf variable “cloudflare_api_token” { type = string sensitive = true }

variable “cloudflare_zone_id” { type = string }

variable “aws_terraform_access_key_id” { type = string sensitive = true }

variable “aws_terraform_secret_access_key” { type = string sensitive = true }

variable “aws_shared_account_id” { type = string }

variable “aws_region” { type = string default = “us-east-1” }

variable “ingress_hostname” { type = string description = “ALB hostname for the EKS ingress” } ```

  • [ ] Step 9: Commit

bash git add modules/frontend/.infra/terraform/ git commit -m "feat: frontend terraform"


Task 9: GitHub Actions workflows

Files: - Create: .github/workflows/review-pr.yml - Create: .github/workflows/check-main.yml - Create: .github/workflows/deploy-staging.yml - Create: .github/workflows/deploy-production.yml

  • [ ] Step 1: Criar review-pr.yml

```yaml # .github/workflows/review-pr.yml name: review-pr

on: pull_request:

env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs: changes: name: detect changes runs-on: ubuntu-latest outputs: frontend: ${{ steps.filter.outputs.frontend }} backend: ${{ steps.filter.outputs.backend }} frontend_infra: ${{ steps.filter.outputs.frontend_infra }} backend_infra: ${{ steps.filter.outputs.backend_infra }} steps: - uses: actions/checkout@v5 - uses: dorny/paths-filter@v4 id: filter with: filters: | frontend: - ‘modules/frontend/’ backend: - ‘modules/backend/’ frontend_infra: - ‘modules/frontend/.infra/terraform/’ backend_infra: - ‘modules/backend/.infra/terraform/’

check-frontend: needs: changes if: needs.changes.outputs.frontend == ‘true’ uses: ibft-corp/pipelines/.github/workflows/check.yml@main with: app_name: trgclub module: frontend version_tag: ${{ github.sha }} secrets: GH_COMMONS_READ_TOKEN: ${{ secrets.GH_COMMONS_READ_TOKEN }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

check-backend: needs: changes if: needs.changes.outputs.backend == ‘true’ uses: ibft-corp/pipelines/.github/workflows/check.yml@main with: app_name: trgclub module: backend version_tag: ${{ github.sha }} services: database secrets: GH_COMMONS_READ_TOKEN: ${{ secrets.GH_COMMONS_READ_TOKEN }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

infra-frontend: needs: changes if: needs.changes.outputs.frontend_infra == ‘true’ uses: ibft-corp/pipelines/.github/workflows/infra-plan.yml@main with: app_name: trgclub module: frontend environment: staging ward_path: trgclub.infra secrets: GH_COMMONS_READ_TOKEN: ${{ secrets.GH_COMMONS_READ_TOKEN }} WARD_KEY: ${{ secrets.WARD_KEY }}

infra-backend: needs: changes if: needs.changes.outputs.backend_infra == ‘true’ uses: ibft-corp/pipelines/.github/workflows/infra-plan.yml@main with: app_name: trgclub module: backend environment: staging ward_path: trgclub.infra secrets: GH_COMMONS_READ_TOKEN: ${{ secrets.GH_COMMONS_READ_TOKEN }} WARD_KEY: ${{ secrets.WARD_KEY }}

finish: needs: [check-frontend, check-backend] if: always() uses: ibft-corp/pipelines/.github/workflows/check-finish.yml@v4 with: results: ${{ needs.check-frontend.result }},${{ needs.check-backend.result }} ```

  • [ ] Step 2: Criar check-main.yml

```yaml # .github/workflows/check-main.yml name: check-main

on: push: branches: [main] paths: - ‘modules/frontend/’ - ‘modules/backend/’

env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs: changes: name: detect changes runs-on: ubuntu-latest outputs: frontend: ${{ steps.filter.outputs.frontend }} backend: ${{ steps.filter.outputs.backend }} frontend_infra: ${{ steps.filter.outputs.frontend_infra }} backend_infra: ${{ steps.filter.outputs.backend_infra }} steps: - uses: actions/checkout@v5 - uses: dorny/paths-filter@v4 id: filter with: filters: | frontend: - ‘modules/frontend/’ backend: - ‘modules/backend/’ frontend_infra: - ‘modules/frontend/.infra/terraform/’ backend_infra: - ‘modules/backend/.infra/terraform/’

check-frontend: needs: changes if: needs.changes.outputs.frontend == ‘true’ uses: ibft-corp/pipelines/.github/workflows/check.yml@main with: app_name: trgclub module: frontend version_tag: ${{ github.sha }} secrets: GH_COMMONS_READ_TOKEN: ${{ secrets.GH_COMMONS_READ_TOKEN }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

check-backend: needs: changes if: needs.changes.outputs.backend == ‘true’ uses: ibft-corp/pipelines/.github/workflows/check.yml@main with: app_name: trgclub module: backend version_tag: ${{ github.sha }} services: database secrets: GH_COMMONS_READ_TOKEN: ${{ secrets.GH_COMMONS_READ_TOKEN }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

infra-frontend: needs: changes if: needs.changes.outputs.frontend_infra == ‘true’ uses: ibft-corp/pipelines/.github/workflows/infra-plan.yml@main with: app_name: trgclub module: frontend environment: staging ward_path: trgclub.infra secrets: GH_COMMONS_READ_TOKEN: ${{ secrets.GH_COMMONS_READ_TOKEN }} WARD_KEY: ${{ secrets.WARD_KEY }}

infra-backend: needs: changes if: needs.changes.outputs.backend_infra == ‘true’ uses: ibft-corp/pipelines/.github/workflows/infra-plan.yml@main with: app_name: trgclub module: backend environment: staging ward_path: trgclub.infra secrets: GH_COMMONS_READ_TOKEN: ${{ secrets.GH_COMMONS_READ_TOKEN }} WARD_KEY: ${{ secrets.WARD_KEY }} ```

  • [ ] Step 3: Criar deploy-staging.yml

```yaml # .github/workflows/deploy-staging.yml name: deploy-staging

on: push: tags: - ‘v*’ workflow_dispatch:

env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs: require-tag: name: require-tag uses: ibft-corp/pipelines/.github/workflows/require-tag.yml@main

changes: needs: require-tag name: detect changes runs-on: ubuntu-latest outputs: frontend: ${{ steps.filter.outputs.frontend }} backend: ${{ steps.filter.outputs.backend }} frontend_infra: ${{ steps.has_infra.outputs.frontend }} backend_infra: ${{ steps.has_infra.outputs.backend }} steps: - uses: actions/checkout@v5 with: fetch-depth: 0 - name: find previous tag id: prev run: | PREV=$(git tag –sort=-version:refname | grep ‘^v’ | sed -n ‘2p’) echo “tag=${PREV:-HEAD~1}” » $GITHUB_OUTPUT - uses: dorny/paths-filter@v4 id: filter with: base: ${{ steps.prev.outputs.tag }} filters: | frontend: - ‘modules/frontend/’ backend: - ‘modules/backend/’ - name: check terraform exists id: has_infra run: | [ -d modules/frontend/.infra/terraform ] && echo “frontend=true” » $GITHUB_OUTPUT || echo “frontend=false” » $GITHUB_OUTPUT [ -d modules/backend/.infra/terraform ] && echo “backend=true”  » $GITHUB_OUTPUT || echo “backend=false”  » $GITHUB_OUTPUT

backend-infra: name: backend:infra needs: changes if: needs.changes.outputs.backend == ‘true’ && needs.changes.outputs.backend_infra == ‘true’ uses: ibft-corp/pipelines/.github/workflows/infra-apply.yml@main with: app_name: trgclub module: backend environment: staging ward_path: trgclub.infra secrets: GH_COMMONS_READ_TOKEN: ${{ secrets.GH_COMMONS_READ_TOKEN }} WARD_KEY: ${{ secrets.WARD_KEY }}

backend-package: name: backend:package needs: changes if: needs.changes.outputs.backend == ‘true’ uses: ibft-corp/pipelines/.github/workflows/package.yml@main with: app_name: trgclub module: backend version_tag: ${{ github.ref_name }} secrets: GH_COMMONS_READ_TOKEN: ${{ secrets.GH_COMMONS_READ_TOKEN }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

backend-migrate: name: backend:migrate needs: [backend-infra, backend-package] if: always() && needs.backend-package.result == ‘success’ && needs.backend-infra.result != ‘failure’ uses: ibft-corp/pipelines/.github/workflows/migrate.yml@main with: app_name: trgclub module: backend version_tag: ${{ github.ref_name }} environment: staging namespace: trgclub–staging secrets: GH_COMMONS_READ_TOKEN: ${{ secrets.GH_COMMONS_READ_TOKEN }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

frontend-infra: name: frontend:infra needs: changes if: needs.changes.outputs.frontend == ‘true’ && needs.changes.outputs.frontend_infra == ‘true’ uses: ibft-corp/pipelines/.github/workflows/infra-apply.yml@main with: app_name: trgclub module: frontend environment: staging ward_path: trgclub.infra secrets: GH_COMMONS_READ_TOKEN: ${{ secrets.GH_COMMONS_READ_TOKEN }} WARD_KEY: ${{ secrets.WARD_KEY }}

frontend-package: name: frontend:package needs: changes if: needs.changes.outputs.frontend == ‘true’ uses: ibft-corp/pipelines/.github/workflows/package.yml@main with: app_name: trgclub module: frontend version_tag: ${{ github.ref_name }} secrets: GH_COMMONS_READ_TOKEN: ${{ secrets.GH_COMMONS_READ_TOKEN }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

backend-delivery: name: backend:delivery needs: [backend-migrate, frontend-package] if: always() && needs.backend-migrate.result == ‘success’ && needs.frontend-package.result != ‘failure’ uses: ibft-corp/pipelines/.github/workflows/delivery.yml@main with: app_name: trgclub module: backend version_tag: ${{ github.ref_name }} environment: staging environment_url: https://beta.staging.api.trgclub.com namespace: trgclub–staging secrets: GH_COMMONS_READ_TOKEN: ${{ secrets.GH_COMMONS_READ_TOKEN }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

frontend-delivery: name: frontend:delivery needs: [backend-migrate, backend-package, frontend-infra, frontend-package] if: always() && needs.frontend-package.result == ‘success’ && needs.frontend-infra.result != ‘failure’ && needs.backend-migrate.result != ‘failure’ && needs.backend-package.result != ‘failure’ uses: ibft-corp/pipelines/.github/workflows/delivery.yml@main with: app_name: trgclub module: frontend version_tag: ${{ github.ref_name }} environment: staging environment_url: https://beta.staging.trgclub.com namespace: trgclub–staging secrets: GH_COMMONS_READ_TOKEN: ${{ secrets.GH_COMMONS_READ_TOKEN }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

land: name: land needs: [backend-delivery, frontend-delivery] if: always() && (needs.backend-delivery.result == ‘success’ || needs.frontend-delivery.result == ‘success’) uses: ibft-corp/pipelines/.github/workflows/land.yml@main with: app_name: trgclub version_tag: ${{ github.ref_name }} environment: staging namespace: trgclub–staging secrets: GH_COMMONS_READ_TOKEN: ${{ secrets.GH_COMMONS_READ_TOKEN }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} DD_API_KEY: ${{ secrets.DD_API_KEY }} ```

  • [ ] Step 4: Criar deploy-production.yml

```yaml # .github/workflows/deploy-production.yml name: deploy-production

on: push: tags: - ‘v*’ workflow_dispatch:

env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs: require-tag: name: require-tag uses: ibft-corp/pipelines/.github/workflows/require-tag.yml@main

changes: name: detect changes needs: require-tag runs-on: ubuntu-latest outputs: frontend: ${{ steps.filter.outputs.frontend }} backend: ${{ steps.filter.outputs.backend }} frontend_infra: ${{ steps.has_infra.outputs.frontend }} backend_infra: ${{ steps.has_infra.outputs.backend }} steps: - uses: actions/checkout@v5 with: fetch-depth: 0 - name: find previous tag id: prev run: | PREV=$(git tag –sort=-version:refname | grep ‘^v’ | sed -n ‘2p’) echo “tag=${PREV:-HEAD~1}” » $GITHUB_OUTPUT - uses: dorny/paths-filter@v4 id: filter with: base: ${{ steps.prev.outputs.tag }} filters: | frontend: - ‘modules/frontend/’ backend: - ‘modules/backend/’ - name: check terraform exists id: has_infra run: | [ -d modules/frontend/.infra/terraform ] && echo “frontend=true” » $GITHUB_OUTPUT || echo “frontend=false” » $GITHUB_OUTPUT [ -d modules/backend/.infra/terraform ] && echo “backend=true”  » $GITHUB_OUTPUT || echo “backend=false”  » $GITHUB_OUTPUT

backend-infra: name: backend:infra needs: changes if: needs.changes.outputs.backend == ‘true’ && needs.changes.outputs.backend_infra == ‘true’ uses: ibft-corp/pipelines/.github/workflows/infra-apply.yml@main with: app_name: trgclub module: backend environment: production ward_path: trgclub.infra secrets: GH_COMMONS_READ_TOKEN: ${{ secrets.GH_COMMONS_READ_TOKEN }} WARD_KEY: ${{ secrets.WARD_KEY }}

backend-package: name: backend:package needs: changes if: needs.changes.outputs.backend == ‘true’ uses: ibft-corp/pipelines/.github/workflows/package.yml@main with: app_name: trgclub module: backend version_tag: ${{ github.ref_name }} secrets: GH_COMMONS_READ_TOKEN: ${{ secrets.GH_COMMONS_READ_TOKEN }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

backend-migrate: name: backend:migrate needs: [backend-infra, backend-package] if: always() && needs.backend-package.result == ‘success’ && needs.backend-infra.result != ‘failure’ uses: ibft-corp/pipelines/.github/workflows/migrate.yml@main with: app_name: trgclub module: backend version_tag: ${{ github.ref_name }} environment: production namespace: trgclub–production secrets: GH_COMMONS_READ_TOKEN: ${{ secrets.GH_COMMONS_READ_TOKEN }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

frontend-infra: name: frontend:infra needs: changes if: needs.changes.outputs.frontend == ‘true’ && needs.changes.outputs.frontend_infra == ‘true’ uses: ibft-corp/pipelines/.github/workflows/infra-apply.yml@main with: app_name: trgclub module: frontend environment: production ward_path: trgclub.infra secrets: GH_COMMONS_READ_TOKEN: ${{ secrets.GH_COMMONS_READ_TOKEN }} WARD_KEY: ${{ secrets.WARD_KEY }}

frontend-package: name: frontend:package needs: changes if: needs.changes.outputs.frontend == ‘true’ uses: ibft-corp/pipelines/.github/workflows/package.yml@main with: app_name: trgclub module: frontend version_tag: ${{ github.ref_name }} secrets: GH_COMMONS_READ_TOKEN: ${{ secrets.GH_COMMONS_READ_TOKEN }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

backend-delivery: name: backend:delivery needs: [backend-migrate, frontend-package] if: always() && needs.backend-migrate.result == ‘success’ && needs.frontend-package.result != ‘failure’ uses: ibft-corp/pipelines/.github/workflows/delivery.yml@main with: app_name: trgclub module: backend version_tag: ${{ github.ref_name }} environment: production environment_url: https://beta.api.trgclub.com namespace: trgclub–production secrets: GH_COMMONS_READ_TOKEN: ${{ secrets.GH_COMMONS_READ_TOKEN }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

frontend-delivery: name: frontend:delivery needs: [backend-migrate, backend-package, frontend-infra, frontend-package] if: always() && needs.frontend-package.result == ‘success’ && needs.frontend-infra.result != ‘failure’ && needs.backend-migrate.result != ‘failure’ && needs.backend-package.result != ‘failure’ uses: ibft-corp/pipelines/.github/workflows/delivery.yml@main with: app_name: trgclub module: frontend version_tag: ${{ github.ref_name }} environment: production environment_url: https://beta.trgclub.com namespace: trgclub–production secrets: GH_COMMONS_READ_TOKEN: ${{ secrets.GH_COMMONS_READ_TOKEN }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

land: name: land needs: [backend-delivery, frontend-delivery] if: always() && (needs.backend-delivery.result == ‘success’ || needs.frontend-delivery.result == ‘success’) uses: ibft-corp/pipelines/.github/workflows/land.yml@main with: app_name: trgclub version_tag: ${{ github.ref_name }} environment: production namespace: trgclub–production secrets: GH_COMMONS_READ_TOKEN: ${{ secrets.GH_COMMONS_READ_TOKEN }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} DD_API_KEY: ${{ secrets.DD_API_KEY }} ```

  • [ ] Step 5: Commit

bash git add .github/workflows/ git commit -m "feat: github actions workflows"


Files: - Create: .commons symlink

  • [ ] Step 1: Criar symlink .commons

bash ln -s /Users/oporpino/workspace/wehive.tech/commons .commons

  • [ ] Step 2: Verificar estrutura completa

bash # Verificar que os arquivos críticos existem ls modules/backend/.infra/k8s/overlays/staging/ ls modules/backend/.infra/terraform/ ls modules/frontend/.infra/k8s/overlays/production/ ls modules/frontend/.infra/terraform/ ls .github/workflows/

  • [ ] Step 3: Verificar kustomize (todos os overlays)

bash kubectl kustomize modules/backend/.infra/k8s/overlays/staging kubectl kustomize modules/backend/.infra/k8s/overlays/production kubectl kustomize modules/frontend/.infra/k8s/overlays/staging kubectl kustomize modules/frontend/.infra/k8s/overlays/production

Expected: todos renderizam sem erro.

  • [ ] Step 4: Commit symlink (se rastreado)

bash git add .commons 2>/dev/null || true git commit -m "feat: commons symlink" 2>/dev/null || echo "nothing to commit"