Skip to content
SubnomicDocs

Docs · Registry

Updated

Push images from GitHub Actions

Build in your pipeline and push to your workspace’s private registry with a robot account: a credential that can push to the repositories you name, and nothing else.

Before you start

Images live at registry.subnomic.com/<workspace>/<repository>:<tag>. The examples below use the workspace acme and the repository api.

Create the repository

A first push creates a repository on its own, but a robot can only be limited to a repository that already exists. So create it first: Registry → Repositories → New repository.

Create a robot account

Under Registry → Robots → New robot:

  • Name — for example github-ci. Its username becomes robot$github-ci.
  • Repositories — pick api, with pull and push. Leave delete off: a build has no reason to remove images. All repositories works too, but a credential that can push everywhere can overwrite everything.
  • Expiry — in days, or 0 for none. An expired robot cannot sign in.

The secret is shown once, with the username and a ready docker login line; only a hash of it is kept. In GitHub, add both under Settings → Secrets and variables → Actions, as SUBNOMIC_REGISTRY_USER and SUBNOMIC_REGISTRY_SECRET.

Use a robot, not your own login. A personal password is refused for any account with two-factor authentication, and the registry accepts no workspace API keys.

The workflow

Save this as .github/workflows/image.yml. It pushes every commit to main as its commit SHA, and every v* tag under its own name.

name: Image

on:
  push:
    branches: [main]
    tags: ['v*']

permissions:
  contents: read

jobs:
  image:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: docker/setup-buildx-action@v4
      - uses: docker/login-action@v4
        with:
          registry: registry.subnomic.com
          username: ${{ secrets.SUBNOMIC_REGISTRY_USER }}
          password: ${{ secrets.SUBNOMIC_REGISTRY_SECRET }}
      - uses: docker/build-push-action@v7
        with:
          push: true
          tags: |
            registry.subnomic.com/acme/api:${{ github.sha }}
            registry.subnomic.com/acme/api:${{ github.ref_name }}

What happens on a push

  • The secret buys a short-lived token. The login trades the robot’s secret for a registry token that lasts minutes; that token is what the push carries.
  • Only new layers travel. Docker asks before it uploads each layer, and a layer the registry already stores is skipped.
  • Every tagged push is scanned, in the background. A finding never blocks the push; it shows on the tag with the version that fixes it. See Image security.
  • An immutable tag stays put. If the repository marks tags immutable, pushing different content to an existing tag is refused with 409 DENIED — another reason to tag by commit SHA.

Multi-architecture images

The registry takes OCI image indexes and Docker manifest lists, so one tag can carry several platforms. Add them to the build step:

      - uses: docker/build-push-action@v7
        with:
          push: true
          platforms: linux/amd64,linux/arm64
          tags: registry.subnomic.com/acme/api:${{ github.sha }}

Building for a platform other than the runner’s needs emulation (QEMU) or a runner of that platform.

Pulling where you deploy

Give the deploying side a robot of its own, with pull only: a server that can pull cannot push a replacement.

echo "$SUBNOMIC_REGISTRY_SECRET" | docker login registry.subnomic.com -u 'robot$deployer' --password-stdin
docker pull registry.subnomic.com/acme/api:<commit sha>

Single-quote the username in a shell. Unquoted, $deployer is read as a variable and the login sends robot.

Rotating and revoking

On the robot’s row you can disable it, rotate its secret or delete it. Each stops it signing in at once. A token it already holds keeps working until that token expires, minutes later, so a push already under way can finish.

When it fails

You seeWhat it means
docker login fails with 401The secret is wrong, rotated or deleted, or the robot is disabled or expired.
The push to another repository is refusedThe robot is limited to the repositories it was given. Add the repository to it, or use a robot made for that one.
403 DENIED, storage or repository quota exceededThe workspace has reached a limit its operators set. Layers it already stores still go through.
409 DENIED on a tag that existsThe tag is immutable. Push a new tag.

Repositories, tags and retention are in Container registry. Deploying a static site from the same pipeline is in Deploy from GitHub Actions.