Skip to content

Publishing to repo.protequ.com

repo.protequ.com is the Protequ Forgejo instance. It hosts the container registry every PQ installation pulls from, and the NuGet feed your adapter builds against.

You publish exactly the way Protequ's own adapters are published: a tag push in your repository triggers a workflow that logs into the registry and pushes a semver-tagged image into the pq namespace.


Credentials

Protequ issues you a Forgejo account with a personal access token. The same account covers:

Use Endpoint
Container registry (push) repo.protequ.com
NuGet feed (read) https://repo.protequ.com/api/packages/PQ/nuget/index.json

In GitHub, store them as repository secrets — PQ_REGISTRY_USERNAME and PQ_REGISTRY_TOKEN. Never commit them, and never bake them into the final image stage.


Image naming

repo.protequ.com/pq/<image>:<version>
                  │    │        └── semver, e.g. 1.0.0 or 1.0.0-rc.1
                  │    └── your image name: lowercase, "pq" prefix; hyphens are allowed
                  └── the namespace every PQ install scans

Only images under pq/ are discovered — the installer and the public catalog both filter on that namespace.

The installer derives several things from <image>, which is why the name matters and must never change between releases:

From image pqacme Derived
Container name adapter-acme (the pq prefix is stripped)
Compose profile pqacme
Tag override variable PQACME_TAG — lets an operator pin one adapter to a specific version
nginx route (UI adapters) /adapters/<ui.slug>/, defaulting to acme

If the image name contains a hyphen, only the tag override variable uses an underscore: pq-tools becomes PQ_TOOLS_TAG. The registry image, Compose profile, and container identity keep the original image name.


Tagging rules

  • The image tag is the version. Valid semver only: ^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$
  • A prerelease is any version containing a - (1.0.0-rc.1, 1.2.0-beta.2).
  • A prerelease must never move latest. latest is the stable channel that fresh installs land on; publishing an RC to it ships unreleased code to every new customer. Push a prerelease under its version tag only.
  • A stable release pushes two tags: 1.0.0 and latest.
  • Never re-push a version tag with different content. Bump the version instead — installations pin digests and will not notice a silent replacement.

GitHub Actions example

A complete, minimal release workflow. Push the tag v1.0.0 and it publishes repo.protequ.com/pq/pqacme:1.0.0 plus :latest; push v1.0.0-rc.1 and it publishes the version tag only.

name: Release adapter image

on:
  push:
    tags:
      - "v*.*.*"
  workflow_dispatch:
    inputs:
      version:
        description: "Semver version (example: 1.0.0-rc.1)"
        required: true
        type: string

permissions:
  contents: read

env:
  REGISTRY: repo.protequ.com
  NAMESPACE: pq
  IMAGE: pqacme

jobs:
  release:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Resolve version and tags
        id: rel
        shell: bash
        run: |
          if [ -n "${{ inputs.version }}" ]; then
            VERSION="${{ inputs.version }}"
          else
            VERSION="${GITHUB_REF_NAME#v}"
          fi

          if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$ ]]; then
            echo "::error::Invalid semver: $VERSION"
            exit 1
          fi

          REF="${REGISTRY}/${NAMESPACE}/${IMAGE}"
          TAGS="${REF}:${VERSION}"

          # A prerelease (version contains "-") is published under its own tag
          # only — it must never move the stable `latest` pointer.
          case "$VERSION" in
            *-*) echo "prerelease: not tagging latest" ;;
            *)   TAGS="${TAGS}"$'\n'"${REF}:latest" ;;
          esac

          echo "version=$VERSION" >> "$GITHUB_OUTPUT"
          {
            echo "tags<<EOF"
            echo "$TAGS"
            echo "EOF"
          } >> "$GITHUB_OUTPUT"

      - name: Log in to the Protequ registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ secrets.PQ_REGISTRY_USERNAME }}
          password: ${{ secrets.PQ_REGISTRY_TOKEN }}

      - name: Set up Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build and push
        id: build
        uses: docker/build-push-action@v6
        with:
          context: .
          file: ./Dockerfile
          platforms: linux/amd64
          push: true
          tags: ${{ steps.rel.outputs.tags }}
          build-args: |
            NUGET_SOURCE_URL=https://repo.protequ.com/api/packages/PQ/nuget/index.json
            NUGET_SOURCE_USER=${{ secrets.PQ_REGISTRY_USERNAME }}
            NUGET_SOURCE_PASS=${{ secrets.PQ_REGISTRY_TOKEN }}

      - name: Summary
        shell: bash
        run: |
          {
            echo "## ${IMAGE} ${{ steps.rel.outputs.version }}"
            echo "- Digest: \`${{ steps.build.outputs.digest }}\`"
            echo "### Published tags"
            while IFS= read -r t; do [ -n "$t" ] && echo "- \`$t\`"; done <<< "${{ steps.rel.outputs.tags }}"
          } >> "$GITHUB_STEP_SUMMARY"

Optional, recommended: a vulnerability gate before the image reaches customers. Protequ's own pipeline fails the release on any fixable CRITICAL finding:

docker run --rm ghcr.io/aquasecurity/trivy:0.58.1 image \
  --severity CRITICAL --ignore-unfixed --exit-code 1 \
  "repo.protequ.com/pq/pqacme@${DIGEST}"

Not required to publish, but a customer's security review will ask.


Manual push

For a one-off or a first trial, without CI:

docker login repo.protequ.com -u <your-username>
docker build -t repo.protequ.com/pq/pqacme:1.0.0 --build-arg NUGET_SOURCE_URL=https://repo.protequ.com/api/packages/PQ/nuget/index.json --build-arg NUGET_SOURCE_USER=<user> --build-arg NUGET_SOURCE_PASS=<token> .
docker push repo.protequ.com/pq/pqacme:1.0.0

For a stable release, also move latest:

docker tag repo.protequ.com/pq/pqacme:1.0.0 repo.protequ.com/pq/pqacme:latest && docker push repo.protequ.com/pq/pqacme:latest

Verifying the publish

1. The tag exists

curl -su "<user>:<token>" https://repo.protequ.com/v2/pq/pqacme/tags/list

2. The labels are what you expect — the reliable check is to pull and inspect:

docker pull repo.protequ.com/pq/pqacme:1.0.0 && docker inspect repo.protequ.com/pq/pqacme:1.0.0 --format '{{json .Config.Labels}}'

To reproduce exactly what the installer does — manifest, then config blob:

curl -su "<user>:<token>" -H "Accept: application/vnd.oci.image.index.v1+json, application/vnd.oci.image.manifest.v1+json" https://repo.protequ.com/v2/pq/pqacme/manifests/latest

Take .config.digest from that response (or from the linux/amd64 child manifest if it is an index) and fetch the blob:

curl -su "<user>:<token>" https://repo.protequ.com/v2/pq/pqacme/blobs/<config-digest>

.config.Labels in that blob is the exact input the catalog reads.

3. It appears in the installer — run the PQ installer's adapter selection step. Your adapter should be listed under its category with the title and description from your labels.


Troubleshooting

Symptom Cause
unauthorized: authentication required on push Token missing, expired, or not passed. Re-run docker login; in CI check the secret name.
denied: requested access to the resource is denied Your account has no write access to pq/, or you pushed to a different namespace.
Image pushed, but absent from the installer catalog Almost always org.protequ.adapter="true" missing or misspelled. Check with docker inspect. Also confirm the repository is under pq/.
Adapter shows up flagged as a prerelease Only prerelease tags exist — there is no latest. Publish a stable version.
A label change had no effect Labels live in the image config. Retagging an existing digest does nothing; rebuild and push. And if you only pushed the version tag, latest still resolves to the old digest.
Buildx TLS error against the registry Only on hosts that reach the registry over an internal, non-TLS path. Add an insecure entry for repo.protequ.com to your buildkitd config. Not needed from GitHub-hosted runners.
dotnet restore cannot find Pq.Adapters.Framework The feed was not added inside the build stage, or the credentials were not passed as build args.