GitHub Actions
Mint the key
In the panel, create a project key for the project you are pushing to, scoped to the one repository the workflow builds:
repositories:push:acme/apiGive it an expiry. A year is reasonable for a CI key you will rotate deliberately; 90 days is better if you have the discipline.
Add it to the repository as an Actions secret named LAZER_KEY.
The workflow
name: build
on:
push:
branches: [main]
jobs:
image:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: lazer.sh
username: ci
password: ${{ secrets.LAZER_KEY }}
- uses: docker/build-push-action@v6
with:
push: true
tags: |
lazer.sh/acme/api:${{ github.sha }}
lazer.sh/acme/api:latest
cache-from: type=gha
cache-to: type=gha,mode=maxThe username is not checked when you present a key — the scopes decide. Use something
descriptive so the audit trail reads well.
Multi-architecture
Add QEMU and a platform list. The push cost is the same because layers are deduplicated:
- uses: docker/setup-qemu-action@v3
- uses: docker/build-push-action@v6
with:
push: true
platforms: linux/amd64,linux/arm64
tags: lazer.sh/acme/api:${{ github.sha }}Tagging by commit, and retention
Tagging every build with github.sha gives you a precise rollback target, but it also
means a tagged image per commit forever. Pair it with a retention rule — keep the last 50
tagged, expire untagged after 7 days — and the cost stays flat. See
retention.
Pulling in a later job
Deploy jobs need a separate key scoped to pull. Do not reuse the push key:
- uses: docker/login-action@v3
with:
registry: lazer.sh
username: deploy
password: ${{ secrets.LAZER_PULL_KEY }}If the push fails
denied: insufficient scope means the key's scope does not cover the repository in the tag
— check for a typo in the project or repository segment. denied: quota exceeded means the
project is out of allocation. Both fail fast, before any layer uploads.