fix(ci): publish container under token account namespace

This commit is contained in:
Simo committed 2026-09-09 19:53:16 +02:00
1 parent c389c3893d
commit 867113d5d4
5 files changed
+35 -6

No files matched your search

+1
View File
@@ -98,6 +98,7 @@ jobs:
env:
REGISTRY_SERVER: ${{ gitea.server_url }}
REGISTRY_REPOSITORY: ${{ gitea.repository }}
REGISTRY_NAMESPACE: ${{ vars.CONTAINER_REGISTRY_NAMESPACE }}
REGISTRY_USER: ${{ secrets.CONTAINER_REGISTRY_USER }}
REGISTRY_TOKEN: ${{ secrets.CONTAINER_REGISTRY_TOKEN }}
run: bash scripts/publish-container.sh
+1
View File
@@ -29,6 +29,7 @@ jobs:
env:
REGISTRY_SERVER: ${{ gitea.server_url }}
REGISTRY_REPOSITORY: ${{ gitea.repository }}
REGISTRY_NAMESPACE: ${{ vars.CONTAINER_REGISTRY_NAMESPACE }}
REGISTRY_USER: ${{ secrets.CONTAINER_REGISTRY_USER }}
REGISTRY_TOKEN: ${{ secrets.CONTAINER_REGISTRY_TOKEN }}
run: bash scripts/publish-container.sh
+13 -3
View File
@@ -219,9 +219,19 @@ After changing `.env`, recreate the container; an image rebuild is not required.
To publish from Gitea:
Gitea packages belong to an account or organization, independently of repository
permissions. Publication defaults to the lowercase `CONTAINER_REGISTRY_USER`
namespace, so a Simo token publishes `simo/epicnext-cms` even though the Git
repository belongs to remco. Set the Actions variable
`CONTAINER_REGISTRY_NAMESPACE` only to override this (for example, an organization
where the token account has package write access). Keep the login username and
token from the same account. Changing namespace also changes the image URL used
by installations; existing remco image tags are not moved automatically.
1. In the repository's Actions secrets, configure `CONTAINER_REGISTRY_USER` and
`CONTAINER_REGISTRY_TOKEN`. Use a Gitea access token with package read/write
permission belonging to an account allowed to publish under the repository owner.
permission belonging to the login account. For the Simo token, set
`CONTAINER_REGISTRY_USER=Simo`; the default package namespace will be `simo`.
2. Every push to `main` or `master` automatically builds and publishes the images
after the CI checks and production deployment succeed. Pull requests do not
publish images. The publication job builds from committed source only and checks
@@ -235,12 +245,12 @@ To publish from Gitea:
migrations image is used temporarily for the matching database migrations.
For this repository the image base is
`gitlab.epicnabbo.nl/remco/epicnext-cms`. Package access is controlled by Gitea.
`gitlab.epicnabbo.nl/simo/epicnext-cms`. Package access is controlled by Gitea.
For private packages, run `docker login gitlab.epicnabbo.nl` on the installation
with a token that can read packages. Then update with:
```bash
CMS_IMAGE_REPOSITORY=gitlab.epicnabbo.nl/remco/epicnext-cms \
CMS_IMAGE_REPOSITORY=gitlab.epicnabbo.nl/simo/epicnext-cms \
CMS_PUBLIC_URL=https://your-hotel.example \
bash scripts/docker-update.sh
```
+6
View File
@@ -11,6 +11,12 @@ registry="${registry%/}"
[[ "$REGISTRY_SERVER" = https://* && "$registry" != */* ]] || { echo "Registry must use HTTPS at the Gitea server root" >&2; exit 1; }
repository="${REGISTRY_REPOSITORY,,}"
[[ "$repository" =~ ^[a-z0-9._-]+/[a-z0-9._-]+$ ]] || exit 1
# Gitea packages belong to a user/organization, independently of repository ACLs.
# A collaborator token cannot publish to another user's personal namespace.
namespace="${REGISTRY_NAMESPACE:-$REGISTRY_USER}"
namespace="${namespace,,}"
[[ "$namespace" =~ ^[a-z0-9][a-z0-9._-]*$ ]] || { echo "Invalid registry namespace; use a Gitea username or organization" >&2; exit 1; }
repository="$namespace/${repository#*/}"
image="$registry/$repository:$sha"
# Isolate credentials from the self-hosted runner's normal Docker configuration.
export DOCKER_CONFIG
+14 -3
View File
@@ -17,7 +17,7 @@ const bash =
.find((path) => existsSync(path)) ?? "bash")
: "bash";
const sha = "a".repeat(40);
function simulate(scenario: string) {
function simulate(scenario: string, namespace = "") {
const dir = mkdtempSync(join(tmpdir(), "cms-publish-test-"));
try {
const result = spawnSync(
@@ -35,7 +35,8 @@ function simulate(scenario: string) {
SCENARIO: scenario,
REGISTRY_SERVER: "https://registry.invalid",
REGISTRY_REPOSITORY: "owner/cms",
REGISTRY_USER: "fixture",
REGISTRY_USER: "Simo",
REGISTRY_NAMESPACE: namespace,
REGISTRY_TOKEN: "fixture-only",
},
},
@@ -51,7 +52,7 @@ describe("verified application image reuse", () => {
it("reuses only the exact image digest that passed deployment checks", () => {
const calls = simulate("verified");
expect(calls).toContain(
`docker tag sha256:candidate registry.invalid/owner/cms:${sha}`,
`docker tag sha256:candidate registry.invalid/simo/cms:${sha}`,
);
expect(calls).not.toContain("docker build --network=host --build-arg");
expect(
@@ -67,3 +68,13 @@ describe("verified application image reuse", () => {
},
);
});
it("uses the token account namespace instead of the repository owner", () => {
const calls = simulate("verified");
expect(calls).toContain(`docker push registry.invalid/simo/cms:${sha}`);
expect(calls).not.toContain("registry.invalid/owner/cms");
});
it("supports an explicit organization namespace", () => {
const calls = simulate("verified", "My-Org");
expect(calls).toContain(`docker push registry.invalid/my-org/cms:${sha}`);
});