Skip to content

Docker Build & Deploy — middag-io

Build multi-arch, push GHCR/ECR, deploy EC2, integração 1Password Connect. Referências: ADR-007, ADR-008, ADR-005.

Arquitetura

GitHub Actions

     ├── docker buildx build --platform linux/arm64
     │        │
     │        ▼
     │   ghcr.io/middag-io/{repo}/{service}:{tag}   ← GHCR (sempre)
     │        │
     │        ├── (se PUSH_TO_ECR=true)
     │        │   crane copy → ECR                   ← ECR (opcional)
     │        │
     │   SCP: docker-compose.yml, .env.tpl, Makefile
     │        │
     │   SSH: docker compose pull
     │        op inject .env.tpl → .env              ← 1Password Connect
     │        docker compose up -d


   EC2 (produção/staging)

1. Estratégia de Registry

Veja ADR-007 — Estratégia de Registry Docker para a justificativa completa, padrões de nomenclatura e quando usar ECR.

Resumo: GHCR por padrão (ghcr.io/middag-io/{repo}/{service}:{tag}), ECR opcional por repo via feature flag PUSH_TO_ECR. GITHUB_TOKEN trata autenticação GHCR automaticamente.

Habilitar ECR em um repo

bash
gh variable set PUSH_TO_ECR --body "true" --repo middag-io/docker-wp-my-project
gh variable set ECR_REPO --body "my-project" --repo middag-io/docker-wp-my-project

2. Configuração de Build

Build multi-arch (ARM64)

Servidores de produção rodam ARM64 (AWS Graviton). Build com --platform linux/arm64:

yaml
- name: Build and push
  run: |
    docker buildx build \
      --platform linux/arm64 \
      -t ghcr.io/middag-io/${{ github.event.repository.name }}/wordpress:${{ github.sha }} \
      -t ghcr.io/middag-io/${{ github.event.repository.name }}/wordpress:latest \
      --push .

Tagging de imagens

TagQuando usadoMutável?
{sha}Todo build (commit SHA)Não
latestTodo build em mainSim
v{x.y.z}Tag de releaseNão

Step de cópia ECR

yaml
- name: Copiar para ECR
  if: vars.PUSH_TO_ECR == 'true'
  env:
    AWS_REGION: ${{ vars.AWS_REGION || 'us-east-1' }}
  run: |
    aws ecr get-login-password --region ${AWS_REGION} \
      | crane auth login ${ECR_REGISTRY} -u AWS --password-stdin
    crane copy \
      ghcr.io/middag-io/${{ github.event.repository.name }}/wordpress:${{ github.sha }} \
      ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${{ vars.ECR_REPO }}:${{ github.sha }}
    crane copy \
      ghcr.io/middag-io/${{ github.event.repository.name }}/wordpress:latest \
      ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${{ vars.ECR_REPO }}:latest

Credenciais AWS vêm do vault 1Password CI-AWS via op run.

3. Deploy para EC2

Fluxo de deploy

  1. SCP de arquivos de config para o servidor
  2. SSH: pull de imagens, injeção de secrets, restart de serviços

Arquivos deployados

ArquivoPropósito
docker-compose.ymlDefinições de serviços
.env.production.tplTemplate de secrets com referências op://
MakefileComandos de deploy (make deploy, make logs)

Injeção de secrets no servidor

Instâncias EC2 rodam 1Password Connect localmente. Secrets são injetados no momento do deploy:

bash
# No servidor (via SSH do CI)
op inject -i .env.production.tpl -o .env

# Exemplo do template:
DB_HOST=op://CI-MYPROJECT/database-production/host
DB_PASSWORD=op://CI-MYPROJECT/database-production/password
REDIS_URL=op://CI-MYPROJECT/redis-production/url

Connect resolve referências op:// usando o servidor Connect local — secrets nunca aparecem em logs do CI ou no GitHub.

Script de deploy (CI)

yaml
- name: Deploy
  env:
    OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
  run: |
    # Carregar chave SSH do 1Password
    eval $(op ssh agent)

    # SCP de arquivos
    scp docker-compose.yml .env.production.tpl Makefile ${{ vars.DEPLOY_USER }}@${{ vars.DEPLOY_HOST }}:${{ vars.DEPLOY_PATH }}/

    # SSH deploy
    ssh $&#123;&#123; vars.DEPLOY_USER &#125;&#125;@$&#123;&#123; vars.DEPLOY_HOST &#125;&#125; << 'EOF'
      cd $&#123;&#123; vars.DEPLOY_PATH &#125;&#125;
      docker compose pull
      op inject -i .env.production.tpl -o .env
      docker compose up -d --remove-orphans
      docker image prune -f
    EOF

4. Estrutura Docker Compose

Layout padrão

docker-wp-my-project/
├── docker-compose.yml
├── docker-compose.override.yml     # overrides de dev local
├── .env.production.tpl             # template op:// 1Password
├── .env.staging.tpl
├── .env.example                    # exemplo para devs
├── Makefile                        # comandos de deploy/ops
├── wordpress/
│   ├── Dockerfile
│   └── conf/
├── nginx/
│   ├── Dockerfile
│   └── conf/
└── .github/
    └── workflows/
        ├── ci.yml
        └── release.yml

Targets do Makefile

makefile
deploy:
	docker compose pull
	op inject -i .env.production.tpl -o .env
	docker compose up -d --remove-orphans

logs:
	docker compose logs -f --tail=100

restart:
	docker compose restart

status:
	docker compose ps

5. Workflow de Operações

Workflow reutilizável para operações comuns no servidor: backup, flush de cache, WP-CLI.

Operações disponíveis

OperaçãoDescriçãoTarget Make
backup-dbExportar banco, baixar como artifactmake backup-db
backup-fullBanco + arquivo de uploadsmake backup
cache-flushFlush Redis + WP object cachemake cache-flush
wp-cliExecutar comando WP-CLI arbitráriomake wp CMD='...'

Setup do workflow chamador

Cada repo docker-wp precisa de um operations.yml fino:

yaml
name: Operations

on:
  workflow_dispatch:
    inputs:
      operation:
        type: choice
        options: [backup-db, backup-full, cache-flush, wp-cli]
      wp-command:
        type: string
        default: "plugin list"

jobs:
  ops:
    uses: middag-io/.github-private/.github/workflows/docker-wp-operations.yml@workflows-v1
    with:
      operation: $&#123;&#123; inputs.operation &#125;&#125;
      wp-command: $&#123;&#123; inputs.wp-command &#125;&#125;
      op-item-ec2: CI-MYPROJECT/AWS-EC2-docker-wp-myproject
      op-item-ssh-key: CI-MYPROJECT/SSH-docker-wp-myproject-dev-key
      op-service-account-secret: OP_SA_MYPROJECT
    secrets: inherit

Itens 1Password necessários

O workflow reutilizável lê dois itens 1Password:

InputCamposExemplo
op-item-ec2EC2/host, EC2/user, EC2/folderCI-MYPROJECT/AWS-EC2-docker-wp-myproject
op-item-ssh-keyprivate_keyCI-MYPROJECT/SSH-docker-wp-myproject-dev-key

Artifacts de backup

Backups são carregados como artifacts do GitHub Actions com retenção de 30 dias. Após download, arquivos de backup são limpos no servidor para evitar acúmulo de disco.

Download em: Actions → Operations → Run → Artifacts.

Validação de build

O step de validação do build-and-deploy.yml faz pull da imagem recém-enviada e executa scripts/validate-build.sh dentro do container via emulação QEMU:

yaml
- name: Validar build
  run: |
    docker run --rm --platform linux/arm64 \
      $&#123;&#123; env.GHCR_REPO &#125;&#125;/wordpress:$&#123;&#123; env.IMAGE_TAG &#125;&#125; \
      bash scripts/validate-build.sh

O script verifica: extensões PHP, WordPress core, autoloader Composer, plugins obrigatórios/premium/customizados, temas, mu-plugins, drop-in de object-cache e ferramentas do sistema.

6. Troubleshooting

ProblemaCausaSolução
Push GHCR negadoToken sem write:packagesAdicionar permissions: packages: write ao job
Login ECR falhouCredenciais AWS expiradas no 1PasswordAtualizar credenciais no vault CI-AWS
crane copy falhaAuth no registry de destino faltandoLogin em ambos os registries antes da cópia
op inject falha no EC2Servidor Connect não está rodandodocker compose up -d op-connect-api op-connect-sync
Build ARM64 falhaSem builder buildx para ARMdocker buildx create --use --platform linux/arm64
Imagem não encontrada no pullRegistry ou tag erradoVerificar se imagem GHCR existe: crane ls ghcr.io/middag-io/{repo}

MIDDAG Tecnologia