diff --git a/.github/workflows/cd-main.yml b/.github/workflows/cd-main.yml index 71ec09d..b2a949b 100644 --- a/.github/workflows/cd-main.yml +++ b/.github/workflows/cd-main.yml @@ -121,10 +121,9 @@ jobs: files: docker/docker-bake.hcl targets: default push: true - set: | - *.cache-from=type=gha - *.cache-to=type=gha,mode=max env: + # Un ámbito de caché por imagen (ver docker/docker-bake.hcl). + CACHE: gha REGISTRY: ghcr.io REPO: ${{ steps.meta.outputs.repo }} TAGS: ${{ steps.meta.outputs.sha_tag }},edge diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7090595..092c125 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,8 +114,10 @@ jobs: targets: default push: false set: | - *.cache-from=type=gha - *.cache-to=type=gha,mode=max *.platform=linux/amd64 env: APP_VERSION: ${{ github.sha }} + # Los ámbitos de caché los declara cada target en docker-bake.hcl: uno + # por imagen. Con un `*.cache-to` comodín las tres compartían ámbito y + # se peleaban por reservar la misma entrada («failed to reserve cache»). + CACHE: gha diff --git a/docker/docker-bake.hcl b/docker/docker-bake.hcl index d45cb42..585769e 100644 --- a/docker/docker-bake.hcl +++ b/docker/docker-bake.hcl @@ -22,6 +22,27 @@ variable "APP_VERSION" { default = "dev" } // sobreescribe PLATFORMS al publicar los manifiestos multi-arquitectura. variable "PLATFORMS" { default = "linux/amd64" } +// Caché de capas entre ejecuciones. Vacío en local —fuera de GitHub Actions no +// existe el servicio y buildx abortaría—; el CI la activa con CACHE=gha. +// +// Cada imagen usa su PROPIO ámbito, y ahí está el motivo de que esto no sea un +// `*.cache-to` a secas en el workflow: `type=gha` sin `scope` usa siempre el +// mismo, así que las tres exportaciones concurrentes se peleaban por reservar +// la misma entrada y la que perdía la carrera tumbaba el build entero con +// «failed to reserve cache». Al depender de qué imagen termina antes, fallaba +// unas veces sí y otras no. +variable "CACHE" { default = "" } + +function "cache_from" { + params = [scopes] + result = CACHE == "gha" ? [for s in scopes : "type=gha,scope=${s}"] : [] +} + +function "cache_to" { + params = [scope] + result = CACHE == "gha" ? ["type=gha,mode=max,scope=${scope}"] : [] +} + group "default" { targets = ["app", "worker", "proxy"] } @@ -43,15 +64,22 @@ target "_common" { } target "app" { - inherits = ["_common"] - target = "app" - tags = [for t in split(",", TAGS) : "${REGISTRY}/${REPO}/app:${t}"] + inherits = ["_common"] + target = "app" + tags = [for t in split(",", TAGS) : "${REGISTRY}/${REPO}/app:${t}"] + cache-from = cache_from(["app"]) + cache-to = cache_to("app") } target "worker" { inherits = ["_common"] target = "worker" tags = [for t in split(",", TAGS) : "${REGISTRY}/${REPO}/worker:${t}"] + // Lee también el ámbito de `app`: el worker es `app` más ffmpeg, así que en + // un arranque en frío se ahorra todas las capas de dependencias. Escribe sólo + // en el suyo, que es lo que evita la colisión. + cache-from = cache_from(["worker", "app"]) + cache-to = cache_to("worker") } // Otro Dockerfile (nada que ver con Node), pero el mismo contexto: necesita @@ -60,4 +88,6 @@ target "proxy" { inherits = ["_common"] dockerfile = "docker/Dockerfile.proxy" tags = [for t in split(",", TAGS) : "${REGISTRY}/${REPO}/proxy:${t}"] + cache-from = cache_from(["proxy"]) + cache-to = cache_to("proxy") }