Guia de CI/CD — middag-io
Referência completa para GitHub Actions, secrets 1Password, deploy AWS e migração legada do Bitbucket.
Sumário
- 1. Visão Geral
- 2. 1Password — Uso no Dia-a-Dia
- 3. GitHub Actions — Workflows Reutilizáveis
- 4. Docker Registry (GHCR / ECR)
- 5. Deploy para Produção
- 6. Adicionando um Novo Projeto
- 7. Adicionando um Novo Secret
- 8. Migração Legada do Bitbucket
- 9. Troubleshooting
1. Visão Geral
Arquitetura
Desenvolvedor ──push──> GitHub (middag-io/{repo})
│
▼
GitHub Actions
(workflows reutilizáveis de middag-io/.github-private)
│
┌────────────┼───────────┐
▼ ▼ ▼
1Password GHCR Cloudflare
(secrets) (imagens) (Pages)
│ │
▼ ▼
EC2 + 1Password Connect
(produção / staging)Decisões Chave
| Decisão | Referência |
|---|---|
| Nomenclatura de repos | ADR-001 |
Modelo de branches (main + develop) | ADR-002 |
| Estratégia de release (release-please) | ADR-003 |
| Estratégia de migração | ADR-004 |
| Integração 1Password | ADR-005 |
2. 1Password — Uso no Dia-a-Dia
Dois Modos
| Contexto | Mecanismo | Quando Usar |
|---|---|---|
| CI (GitHub Actions) | Service Account Token | Workflows, build, test, scripts de deploy |
| Servidores (EC2/Docker) | 1Password Connect | Containers produção/staging, op inject |
Para Desenvolvedores: Como os secrets fluem
1. Secret vive no vault 1Password (ex.: CI-MYPROJECT)
2. Código referencia como op://CI-MYPROJECT/item-name/field-name
3. No CI: 1password/load-secrets-action resolve
4. No servidor: op inject resolve via Connect
5. Desenvolvedor nunca vê ou copia o valor do secretAdicionando um secret a um projeto
Crie o item no vault 1Password apropriado:
Vault Use para CI-SHAREDCompartilhado entre projetos (GHCR, SES, Cloudflare global) CI-AWSCredenciais AWS (ECR, RDS, Lambda) CI-{PROJECT}Específico do projeto (deploy keys, API keys, certificados) PRIVATEChaves privadas, certificados Referencie no seu
.env.*.tplou workflow:# Em .env.production.tpl MY_SECRET="op://CI-MYPROJECT/my-item/my-field"yaml# Em workflow do GitHub Actions - uses: 1password/load-secrets-action@v2 env: OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SA_MYPROJECT }} MY_SECRET: op://CI-MYPROJECT/my-item/my-fieldVerifique se o vault é acessível pelo Service Account ou Connect server correto.
Mudanças de acesso a vaults
| Contexto | O que fazer |
|---|---|
| Connect (servidor) | 1Password web → Integrations → Connect server → adicionar vault. Sem redeploy. |
| Service Account (CI) | Se vault já está no escopo do SA: nada. Se novo vault necessário: criar novo token SA, atualizar secret no GitHub. |
Nomenclatura de Service Account
sa-github-ci-shared → nível de org (CI-SHARED + CI-AWS)
sa-github-ci-myproject → projeto my-project
sa-github-ci-{project} → novo projetoPosicionamento de secrets no GitHub
| Secret | Nível | Onde |
|---|---|---|
OP_SERVICE_ACCOUNT_TOKEN | Org | GitHub org settings → Secrets → Actions |
OP_SA_{PROJECT} | Repo | GitHub repo settings → Secrets → Actions |
OP_CONNECT_TOKEN | Server | Ambiente EC2 (não no GitHub) |
3. GitHub Actions — Workflows Reutilizáveis
Todos os workflows de CI/CD ficam em middag-io/.github-private e são chamados por repos individuais.
Workflows Disponíveis
| Workflow | Propósito | Usado por |
|---|---|---|
wp-plugin-ci.yml | PHP matrix + checks de UI opcionais (configurável via inputs) | Repos wp-plugin-* |
wp-theme-ci.yml | PHP matrix para temas | Repos wp-theme-* |
wp-plugin-post-release.yml | Build ZIP dist, upload no GH Release, trigger privatesatis | Repos wp-plugin-* |
release-please.yml | Version bump, changelog, GitHub Release | Todos os repos |
docs-deploy.yml | Build VitePress + deploy Cloudflare Pages (com gate de hash) | Repos com docs |
docker-build-push.yml | Build multi-arch, push GHCR, cópia ECR opcional | Repos docker-* (planejado) |
docker-deploy.yml | Deploy SSH para EC2 | Repos docker-* (planejado) |
moodle-plugin-ci.yml | Checks moodle-plugin-ci | Repos moodle-* (planejado) |
composer-package-ci.yml | PHPUnit, PHPStan, PHPCS | Repos de libs PHP (planejado) |
Como usar no seu repo
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
ci:
uses: middag-io/.github-private/.github/workflows/wp-plugin-ci.yml@workflows-v1
with:
has-ui: true
has-tests: true
has-rector: true
secrets: inherit# .github/workflows/release.yml
name: Release
on:
push:
branches: [main]
jobs:
release-please:
uses: middag-io/.github-private/.github/workflows/release-please.yml@workflows-v1
permissions:
contents: write
pull-requests: write
secrets: inherit
post-release:
needs: release-please
if: needs.release-please.outputs.release_created == 'true'
uses: middag-io/.github-private/.github/workflows/wp-plugin-post-release.yml@workflows-v1
with:
tag-name: ${{ needs.release-please.outputs.tag_name }}
has-ui: true
has-strauss: true
zip-name: my-plugin
permissions:
contents: write
secrets: inheritInputs de workflow e variáveis
Configuração por repo é passada como inputs de workflow (não variáveis de repo). Secrets e variáveis no nível da org são compartilhados via secrets: inherit.
Inputs de workflow (por repo, passados em with:)
| Input | Workflow | Propósito | Exemplo |
|---|---|---|---|
zip-name | wp-plugin-post-release | Nome da pasta do plugin para ZIP | my-project |
has-ui | wp-plugin-ci / post-release | Plugin tem diretório ui/ | true |
has-strauss | wp-plugin-post-release | Prefixação de vendor com Strauss | true |
has-tests | wp-plugin-ci | Executar composer test | true |
has-phpstan | wp-plugin-ci / theme | Executar análise estática PHPStan | true |
has-cs-fixer | wp-plugin-ci / theme | Executar PHP CS Fixer | true |
has-rector | wp-plugin-ci / theme | Executar Rector | false |
needs-satis-auth | wp-plugin-post-release | Composer precisa auth privatesatis | true |
docs-path | docs-deploy | Diretório de docs VitePress | docs/ |
project-name | docs-deploy | Nome do projeto Cloudflare Pages | my-project-docs |
Secrets de org (compartilhados via secrets: inherit)
| Secret | Visibilidade | Propósito |
|---|---|---|
OP_SERVICE_ACCOUNT_TOKEN | TODOS | 1Password Service Account (org, CI-SHARED + CI-AWS) |
PRIVATESATIS_PASSWORD | TODOS | Senha HTTP Basic para privatesatis.middag.com.br |
PRIVATESATIS_DISPATCH_TOKEN | TODOS | GitHub PAT com escopo repo — trigger repository_dispatch no my-satis-repo |
CLOUDFLARE_API_TOKEN | TODOS | Token API Cloudflare (deploy Pages) |
DOCKERHUB_TOKEN | TODOS | Token push Docker Hub |
NPM_TOKEN | PRIVADO | Token publish npm registry |
SMTP_PASSWORD | PRIVADO | Senha SMTP SES |
PRIVATESATIS_BOT_APP_ID | TODOS | GitHub App ID para privatesatis-bot (auth Composer cross-repo) |
PRIVATESATIS_BOT_PRIVATE_KEY | TODOS | Chave privada (PEM) do GitHub App para privatesatis-bot |
Variáveis de org (compartilhadas via secrets: inherit)
| Variável | Visibilidade | Valor | Propósito |
|---|---|---|---|
PRIVATESATIS_USERNAME | TODOS | github-middag-io | Username HTTP Basic para privatesatis |
CLOUDFLARE_ACCOUNT_ID | TODOS | 32ff8929... | Conta Cloudflare |
DOCKERHUB_USERNAME | TODOS | middagtec | Username push Docker Hub |
SENTRY_ORG | TODOS | middag | Slug da organização Sentry |
SMTP_HOST | PRIVADO | email-smtp.us-east-1... | Host SMTP SES |
SMTP_PORT | PRIVADO | 587 | Porta SMTP SES |
SMTP_USER | PRIVADO | AKIA... | Usuário IAM SMTP SES |
Feature flags — variáveis de repo
Variáveis no nível do repo atuam como feature flags para habilitar steps opcionais do workflow sem alterar o código do workflow. Defina em GitHub repo Settings → Variables → Actions.
| Variável | Escopo | Valor | Propósito |
|---|---|---|---|
PUSH_TO_ECR | Var de repo | true | Habilita crane copy do GHCR para ECR após build (veja ADR-007) |
ECR_REPO | Var de repo | my-project | Nome do repositório ECR (obrigatório quando PUSH_TO_ECR=true) |
Padrão: qualquer step de workflow pode ser condicionado com if: vars.MY_FLAG == 'true'. Isso evita forks de workflow por repo — um workflow reutilizável atende todos os repos, variáveis de repo controlam o comportamento.
4. Docker Registry (GHCR / ECR)
Padrão: GHCR (GitHub Container Registry)
Todas as imagens Docker fazem push para GHCR por padrão:
ghcr.io/middag-io/{repo-name}/{service}:{tag}Exemplo:
ghcr.io/middag-io/docker-wp-my-project/wordpress:a1b2c3d4e5f6
ghcr.io/middag-io/docker-wp-my-project/nginx:latestOpcional: ECR (AWS Elastic Container Registry)
Habilitado por repo via variáveis de repositório:
PUSH_TO_ECR=true
ECR_REPO=my-projectQuando habilitado, docker-build-push.yml usa crane copy para replicar do GHCR para ECR após o build (sem rebuild):
GHCR (alvo do build) ──crane copy──> ECR (fonte do deploy)Credenciais ECR do vault 1Password CI-AWS.
Quando usar ECR
- Servidores de produção na AWS que se beneficiam de pull na mesma região
- Atualmente: apenas
docker-wp-my-projectprecisa de ECR - Padrão para novos projetos: apenas GHCR, a menos que haja necessidade específica
5. Deploy para Produção
Projetos Docker (EC2)
GitHub Actions EC2
│ │
├── Build & push para GHCR │
├── (opcional) crane copy para ECR │
├── SCP: docker-compose.yml, │
│ .env.production.tpl, │
│ Makefile │
├── SSH: docker compose pull │
│ op inject .env.tpl → .env │ ← 1Password Connect (local)
│ docker compose up -d │
└── Cleanup │Release de plugin WordPress
1. Merge PR de develop → main
2. release-please detecta conventional commits em main
3. Cria/atualiza Release PR (version bump + CHANGELOG.md)
4. Merge da Release PR → dispara:
a. GitHub Release criado (com tag)
b. Workflow pós-release: build UI → Strauss → ZIP → upload no Release
c. Trigger rebuild privatesatis (repository_dispatch)
d. Deploy docs para Cloudflare Pages (se aplicável)Release de tema WordPress
Mesmo que plugin, mas versão em style.css + functions.php.
6. Adicionando um Novo Projeto
Checklist
Criar repo seguindo ADR-001:
bashgh repo create middag-io/{name} --private --description "{desc}"Definir branch default como
main, criar branchdevelopAdicionar topics (platform, type, client)
Adicionar CI workflow chamando workflow reutilizável:
yamljobs: ci: uses: middag-io/.github-private/.github/workflows/wp-plugin-ci.yml@workflows-v1 with: has-tests: true secrets: inheritAdicionar config release-please:
- Criar
release-please-config.jsoncom release typesimpleeextra-filespara header de versão - Criar
.release-please-manifest.jsoncom versão atual - Adicionar anotações
// x-release-please-versionnas linhas de versão do arquivo principal de plugin/theme - Adicionar workflow de release chamando
release-please.yml+wp-plugin-post-release.yml
- Criar
Configurar secrets (se projeto precisa de SA próprio):
- Criar Service Account no 1Password:
sa-github-ci-{project} - Conceder acesso aos vaults apropriados
- Adicionar token como repo secret:
OP_SA_{PROJECT}
- Criar Service Account no 1Password:
Definir variáveis de repo (
vars.*) para config de workflowConfigurar proteção de branch (ou verificar se ruleset da org se aplica)
Testar CI — push para develop, abrir PR, merge para main
7. Adicionando um Novo Secret
Árvore de decisão
É compartilhado entre projetos?
├── Sim → Adicionar ao vault CI-SHARED ou CI-AWS
│ (já acessível via SA da org)
│
└── Não → Existe vault do projeto (CI-{PROJECT})?
├── Sim → Adicionar ao CI-{PROJECT}
│ (já acessível via SA do projeto)
│
└── Não → Criar vault CI-{PROJECT}
Criar sa-github-ci-{project} SA
Adicionar OP_SA_{PROJECT} aos secrets do repoApós adicionar o secret
- Referenciar no código:
op://CI-{VAULT}/item-name/field-name - Se usado no workflow CI: adicionar ao bloco env de
1password/load-secrets-action - Se usado no servidor: adicionar ao
.env.production.tpl(Connect resolve automaticamente) - Testar no CI e/ou staging antes de produção
8. Migração Legada do Bitbucket
Wave 1 (WordPress) está COMPLETA. As tabelas abaixo permanecem como referência útil para futuras waves de migração.
Mapeamento de variáveis
| Bitbucket | GitHub Actions |
|---|---|
$BITBUCKET_COMMIT | ${{ github.sha }} |
$BITBUCKET_REPO_SLUG | ${{ github.event.repository.name }} |
$BITBUCKET_REPO_FULL_NAME | ${{ github.repository }} |
$BITBUCKET_BRANCH | ${{ github.ref_name }} |
$BITBUCKET_TAG | ${{ github.ref_name }} (em trigger de tag) |
$BITBUCKET_SSH_KEY_FILE | Deploy key ou action ssh-agent |
$BITBUCKET_BOT_USERNAME | github-actions[bot] |
$BITBUCKET_BOT_PASSWORD | ${{ secrets.GITHUB_TOKEN }} |
Mapeamento Pipe → Action
| Bitbucket Pipe | Equivalente GitHub |
|---|---|
atlassian/bitbucket-upload-file | gh release upload ou actions/upload-artifact |
atlassian/trigger-pipeline | repository_dispatch ou workflow_dispatch |
Etapas de migração Pipeline → Workflow
- Ler
bitbucket-pipelines.yml - Identificar steps, triggers, condições, services
- Mapear para workflow GitHub Actions usando workflows reutilizáveis
- Substituir variáveis BB por equivalentes GH (tabela acima)
- Substituir pipes BB por actions GH (tabela acima)
- Mover variáveis de pipeline BB para 1Password ou variáveis de repo GH
- Testar na branch develop antes de merge para main
O que NÃO é migrado
- Bitbucket Issues e Pull Requests (Jira usado para tracking)
- Bitbucket Downloads (mover backups para S3 ou GitHub Releases)
- Histórico de builds do Pipeline (irrelevante)
Status da Migração
| Wave | Status | Notas |
|---|---|---|
| 0 — Infra | COMPLETO | .github, CI workflows, config da org |
| 1 — WP | COMPLETO | 6 repos migrados, pipelines BB removidos, workflows reutilizáveis + release-please |
| 2 — Libs | Pendente | Renomear + polimento de topics/properties |
| 3 — Apps | Pendente | |
| 4 — Moodle core | Pendente | CI complexo |
| 5 — Moodle sites | Pendente | Migração por site |
9. Troubleshooting
1Password no CI
| Problema | Causa | Solução |
|---|---|---|
could not find item | Vault ou nome de item errado no op:// | Verificar nome do vault + título do item no 1Password |
unauthorized | Token SA não tem acesso ao vault | Verificar permissões do SA no 1Password web |
connect: connection refused | Connect não está rodando (apenas infra) | docker compose up -d op-connect-api op-connect-sync |
GitHub Actions
| Problema | Causa | Solução |
|---|---|---|
secrets: inherit não funciona | Repo não está na org, ou caminho errado | Verificar repo sob org middag-io |
| Workflow reutilizável não encontrado | Ref ou caminho errado | middag-io/.github-private/.github/workflows/{name}.yml@workflows-v1 |
| Permissão negada ao fazer push de tag | Proteção de branch bloqueia bot | Adicionar bot à lista de bypass no ruleset |
Docker
| Problema | Causa | Solução |
|---|---|---|
| Push GHCR negado | Token sem write:packages | Verificar escopo do GHCR_TOKEN no 1Password |
| Login ECR falhou | Credenciais AWS expiradas | Verificar credenciais no vault CI-AWS |
crane copy falha | Auth no registry de destino | Garantir login em ambos os registries antes da cópia |