Skip to content

ci: publish scope worker images + register artifacts (manual trigger) - #228

Merged
sebasnallar merged 1 commit into
betafrom
feat/publish-scope-images
Aug 19, 2026
Merged

ci: publish scope worker images + register artifacts (manual trigger)#228
sebasnallar merged 1 commit into
betafrom
feat/publish-scope-images

Conversation

@sebasnallar

Copy link
Copy Markdown
Contributor

What

A manually-triggered pipeline (Actions → Run workflow) that builds & pushes each scope's worker image to ECR Public and registers it as an oci_image platform artifact (visible-to organization=*) — the exact scopes-lambda flow, fanned out over this repo.

Images

image (nullplatform/scopes/<name>) source notes
containers k8s/ base — FROM worker-bridge + kubectl, helm, aws, tofu, gomplate, yq
scheduled-task scheduled_task/ leaner — kubectl + gomplate
containers-datadog containers + datadog/ metric overlay
containers-azure containers + azure/ overlay + azure-cli
containers-azure-aro containers + azure-aro/ overlay + azure-cli

The containers base bakes the whole repo into /app/pkg, so the overlays are just FROM containers:<version> + a flipped NP_OVERRIDES_PATH — no re-copy. That's why the overlays job needs: bases (base must be pushed first).

Trigger

Run workflow ▸
  version: 0.0.1                     # tags the image + artifact revision
  scope:   all | containers | scheduled-task |
           containers-datadog | containers-azure | containers-azure-aro

all builds the two bases in parallel, then the three overlays. A single scope builds just that one (an overlay assumes containers:<version> is already published).

Required repo config (same as scopes-lambda)

  • Secret AWS_ROLE_ARN_ECR_PUSH — OIDC role for ECR Public push
  • Secret ARTIFACT_NP_API_KEY — np API key (→ NULLPLATFORM_API_KEY)
  • Variable NP_ARTIFACT_NRN — owner NRN for the artifacts

Verified

Built the containers base locally (amd64): tofu 1.10.6, kubectl 1.30.4, helm 3.15.4, aws 2.15.57, gomplate — correct NP_SERVICE_PATH=/app/pkg/k8s + entrypoint, and datadog/azure/azure-aro all baked in. Confirmed the overlay FROM containers:<v> + NP_OVERRIDES_PATH pattern resolves.

Review please

  • Tooling/versions in docker/containers.Dockerfile (kubectl/helm/tofu pins) — set to sensible current versions; confirm against what the scopes target.
  • azure-cli install in the azure overlays is a first cut (pip on alpine, heavy). Swap for a leaner method if the azure steps only need a subset.
  • Nothing runs until someone triggers it.

@null-paorodrigues

Copy link
Copy Markdown
Contributor

Revisé el workflow y hay que alinearlo con el patrón que ya usamos en el org para publicar a ECR Public. El molde es nullplatform/scopes-lambda#36 (mergeado, corriendo hoy) — la idea es que este quede igual, fanned out a las 5 imágenes.

1. Usar el reusable en vez de pasos inline

publish-images.yml copia los pasos de build/push en vez de llamar a nullplatform/actions-nullplatform/.github/workflows/docker-build-push-ecr.yml@main. Además de duplicar, quedó una generación atrás en todas las actions:

reusable este PR
actions/checkout v6 v4
aws-actions/configure-aws-credentials v6 v4
docker/build-push-action v7 v6
docker/setup-qemu / setup-buildx v4 v3
cache type=gha ninguno

Este repo ya consume reusables en pr-checks.yml (pr-checks-go.yml@main, shellcheck.yml@main), así que es la convención acá también.

El reusable cubre lo que necesitás: platforms (multi-arch), build_args (para el BASE_VERSION de los overlays), dockerfile (con context: . y dockerfile: docker/<name>.Dockerfile resuelve bien) y expone el output image_digest.

2. Trigger: tag v* en vez de workflow_dispatch

El repo ya viene taggeando semver (v1.15.1, v1.15.0, v1.14.0…) con CHANGELOG mantenido, así que la versión de las imágenes debería salir de ahí y no de un input manual:

on:
  push:
    tags:
      - 'v*'

Con eso el input version se borra (tag: ${{ github.ref_name }}) y las imágenes quedan atadas a la versión real del repo. El filtro v* ignora los tags viejos sin prefijo (1.11.0, latest) sin hacer nada extra.

El trigger por tag además es lo que espera la configuración de credenciales del lado de AWS, que sigue el mismo patrón que scopes-lambda. Con workflow_dispatch habría que ampliarla, así que mejor no desviarse.

3. El np artifact create va en un job aparte

Hoy está como step dentro del job de build. En el #36 es un job separado con needs, que consume el digest del reusable:

  publish-artifact:
    needs: publish
    runs-on: ubuntu-24.04
    env:
      NULLPLATFORM_API_KEY: ${{ secrets.ARTIFACT_NP_API_KEY }}
      NP_ARTIFACT_NRN: ${{ vars.NP_ARTIFACT_NRN }}
    steps:
      ...
          --digest "${{ needs.publish.outputs.image_digest }}"

4. Cuidado con matrix + outputs (esto es un bug silencioso)

Si el job que llama al reusable usa strategy.matrix con las 5 imágenes, needs.<job>.outputs.image_digest devuelve un solo digest — el último que escribió. Los outputs de un job matrixado se pisan entre sí, no se indexan por matrix key. Resultado: 5 artifacts registrados apuntando todos a la misma imagen, sin que falle nada en el run.

La forma más directa de evitarlo es jobs explícitos por imagen (5 pares build + register), cada uno igual al #36, con needs para el orden base → overlay. Queda verboso pero cada digest queda atado a su imagen.

5. Se pierde provenance: false

El reusable no expone ese input, así que al migrar el digest pasa a ser el del índice OCI con attestations. Es el comportamiento que ya tiene scopes/lambda en producción, así que está OK — pero tenelo presente porque es el digest que se registra en el artifact.


La parte de infra (credenciales, repositorios del registry y el orden de rollout) la manejo yo y te la paso por privado. Nada de eso bloquea la reescritura del workflow: cuando esté con el reusable y el trigger por tag, coordinamos el primer tag.

@sebasnallar

Copy link
Copy Markdown
Contributor Author

Reescrito para seguir el mold de scopes-lambda#36 — gracias por el detalle. Todo aplicado:

1. Reusable en vez de inline ✅ — cada imagen buildea con nullplatform/actions-nullplatform/.github/workflows/docker-build-push-ecr.yml@main (context: . + dockerfile: docker/<name>.Dockerfile). Se van las actions viejas y el cache type=gha sale del reusable.

2. Trigger por tag v* ✅ — se borró workflow_dispatch y el input version; ahora tag: ${{ github.ref_name }}. Los tags viejos sin v quedan ignorados por el filtro.

3. np artifact create en job aparte ✅ — un register-* por imagen, con needs, consumiendo needs.<build>.outputs.image_digest.

4. Matrix + outputs ✅ — no uso matrix. Son jobs explícitos por imagen (5 build + 5 register), cada uno como el #36. Cada digest queda atado a su imagen; los overlays needs: containers para el orden base → overlay y pasan BASE_VERSION=${{ github.ref_name }} para que su FROM containers:<tag> resuelva.

5. provenance: false — anotado. Queda el digest del índice OCI con attestations, igual que scopes/lambda en prod. 👍

Sobre infra (creds/repos del registry/orden de rollout): perfecto, coordinamos el primer tag cuando quieras.

Nota: verifiqué local que la base containers buildea con todo el tooling (tofu 1.10.6, kubectl 1.30.4, helm 3.15.4, aws, gomplate) y que el patrón de overlay FROM containers:<tag> + NP_OVERRIDES_PATH resuelve. El azure-cli de los overlays azure es un primer corte (pip en alpine) — si querés lo afinamos.

@null-paorodrigues null-paorodrigues left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aplicaste los 5 puntos del review anterior — el reusable, el trigger por tag, los jobs explícitos por imagen (con el gotcha del matrix documentado en el header, 👌) y el registro en job aparte. Todo eso queda.

Queda un tema, y es de destino: las imágenes no van a scopes/<name>. Dejé los detalles anclados en las líneas.

Resumen: en ECR el slash forma parte del nombre del repositorio, así que scopes/containers sería un repositorio independiente — y no existe. ECR Public no autocrea repositorios en el push (a diferencia de GHCR o Docker Hub), así que fallaría en vez de crearlo. El repositorio que existe es scopes, donde las imágenes se distinguen por prefijo en el tag: los tags publicados hoy ahí son k8s-0.0.1, k8s-0.0.2, lambda-0.0.1, lambda-0.0.2 (listable sin credenciales, es un registry público).

Los cambios son mecánicos salvo dos que tienen filo: cómo se pasa el tag (comentario en la línea 37) y el FROM de los overlays (comentario en el Dockerfile). Además hay que actualizar el header del archivo (líneas 5-11), que documenta los nombres viejos, y los 5 --repository del np artifact create.

Del lado de las credenciales ya está encaminado y te lo paso por privado.

@null-paorodrigues

Copy link
Copy Markdown
Contributor

Retiro el review anterior: borré los comentarios inline porque estaban equivocados. El destino que tenés en el PR es el correcto, no lo cambies.

Las imágenes van así, un repositorio por imagen:

public.ecr.aws/nullplatform/scopes/containers:<TAG>
public.ecr.aws/nullplatform/scopes/scheduled-task:<TAG>
public.ecr.aws/nullplatform/scopes/containers-datadog:<TAG>
public.ecr.aws/nullplatform/scopes/containers-azure:<TAG>
public.ecr.aws/nullplatform/scopes/containers-azure-aro:<TAG>

Que es el mismo esquema que scopes/lambda:<TAG>, ya publicado. Así que image_name: scopes/<name>, el tag: ${{ github.ref_name }} y el FROM public.ecr.aws/nullplatform/scopes/containers:${BASE_VERSION} de los overlays quedan como están.

Lo que había planteado (un repositorio scopes con prefijo en el tag, continuando k8s-* y lambda-*) era una alternativa que ya no aplica. Nada de eso hay que tocar.

Lo que sigue en pie del primer review y ya resolviste: el reusable, el trigger por tag, los jobs explícitos por imagen y el registro en job aparte. Eso está bien como quedó.

Único punto que queda abierto, menor: el destino está declarado dos veces por imagen — el image_name del build y el --repository del np artifact create. No hay nada que los ate, así que si uno cambia y el otro no, el artifact queda apuntando a una imagen que no se pusheó, con el run en verde. Vale derivarlos de un mismo valor.

Los repositorios de ECR todavía no existen (salvo scopes/lambda) y no se crean solos en el push — de eso me encargo yo junto con las credenciales, y te aviso cuando esté para que el primer tag salga verde.

Comment thread .github/workflows/publish-images.yml
Comment thread .github/workflows/publish-images.yml Outdated
fedemaleh
fedemaleh previously approved these changes Aug 19, 2026
@sebasnallar
sebasnallar changed the base branch from main to beta August 19, 2026 13:40
@sebasnallar
sebasnallar dismissed fedemaleh’s stale review August 19, 2026 13:40

The base branch was changed.

… artifacts

Manually-verified publish-images workflow (v* tag trigger) that builds each scope
worker image via the org reusable ECR workflow and registers it as an oci_image
platform artifact (visible-to organization=*), fanned out over the 3 images:
containers (k8s base), scheduled-task, containers-datadog (metric overlay).

Uses the existing NP_API_KEY secret. Rebased onto beta so the PR carries only the
feature (no main-only drift such as the cloudwatch deployment annotations).

CHANGELOG: Publish containers and scheduled task scopes as docker images.
@sebasnallar
sebasnallar force-pushed the feat/publish-scope-images branch from 3297bdb to 75bc305 Compare August 19, 2026 13:44
@sebasnallar
sebasnallar merged commit 76bd278 into beta Aug 19, 2026
3 checks passed
@sebasnallar
sebasnallar deleted the feat/publish-scope-images branch August 19, 2026 18:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants