Skip to content

ci: build + publish aisix container image to GHCR - #27

Merged
moonming merged 2 commits into
mainfrom
feat/docker-image-publish
Apr 23, 2026
Merged

ci: build + publish aisix container image to GHCR#27
moonming merged 2 commits into
mainfrom
feat/docker-image-publish

Conversation

@moonming

Copy link
Copy Markdown
Collaborator

Summary

Adds a root Dockerfile and a .github/workflows/docker-image.yml workflow that publishes the aisix AI gateway image to ghcr.io/moonming/ai-gateway.

Motivation

Downstream consumers need a pre-built container image to wire aisix into integrated test stacks without cloning this repo and running cargo build from scratch each time.

The immediate consumer is the aisix.cloud integrated E2E suite at api7/AISIX-Cloud#3, whose e2e/compose.yml assumes ghcr.io/api7/aisix-dp:aisix-e2e is available. After this PR lands and publishes, api7.cloud will just pull from this namespace (or re-tag under api7/ in a small downstream workflow). The AISIX.cloud PR #3 also documents the overall E2E strategy (5 happy-path scenarios verifying DP ↔ control plane cooperation).

Dockerfile design

  • Stage 1 — builder: rust:1.93-bookworm, installs protobuf-compiler for prost/tonic-build, uses BuildKit cache mounts on $CARGO_HOME/registry and target/ so incremental pushes reuse deps.
  • Stage 2 — runtime: debian:bookworm-slim, non-root uid 10001, tini as PID 1 for clean signal forwarding, ca-certificates for outbound TLS. Exposes :3000 (proxy) and :3001 (admin) to match config.example.yaml. Config path is /etc/aisix/config.yaml by default; override with --config or AISIX_CONFIG.

Workflow tag strategy

Trigger Tags pushed
push to main :main, :sha-<shortsha>
push tag vX.Y.Z :X.Y.Z, :X.Y, :X, :latest
pull_request on Dockerfile / crates / workflow build-only (no push) — serves as a sanity check that the image still builds
workflow_dispatch manual; tag input publishes an additional custom tag (e.g. aisix-e2e for downstream integration testing)

Uses docker/metadata-action@v5 + docker/build-push-action@v6 with GitHub Actions cache (type=gha).

Image destination

ghcr.io/moonming/ai-gateway — same GHCR namespace as this repository. Downstream organisations can re-tag under their own namespace with a small follow-up workflow if desired (e.g. docker pull + docker tag + docker push to ghcr.io/api7/aisix-dp:<tag>).

Test plan

  • cargo fmt --check / cargo clippy untouched (no code changes, existing ci.yml still runs)
  • PR-trigger build-only job succeeds (validates the Dockerfile on this very PR without publishing)
  • Post-merge, first push to main publishes ghcr.io/moonming/ai-gateway:main and :sha-<shortsha>
  • Manual workflow_dispatch with tag=aisix-e2e publishes the tag used by aisix.cloud E2E

No runtime code changes

This PR is strictly build + CI. The binary's behaviour, CLI flags, and config schema are unchanged.

Adds a Dockerfile at the repo root and a docker-image.yml workflow
that publishes the aisix AI gateway image to ghcr.io/moonming/ai-gateway.

Motivation: downstream consumers (notably the aisix.cloud integrated
E2E suite in api7/AISIX-Cloud#3) need a pre-built container image
to exercise "AISIX DP ↔ control plane" cooperation in CI. Previously
each consumer had to clone this repo and cargo build themselves.

Dockerfile (root):

  Stage 1   rust:1.93-bookworm   cargo build --release --bin aisix
                                  (rust-toolchain.toml pins 1.93.1)
                                  protoc installed for prost/tonic-build
                                  BuildKit cache mounts on cargo registry
                                  and target/ to speed incremental builds
  Stage 2   debian:bookworm-slim  runs as uid 10001 non-root
                                  tini as PID 1 for signal forwarding
                                  ca-certificates for outbound TLS
                                  expects /etc/aisix/config.yaml at runtime
                                  (override via --config or AISIX_CONFIG env)

Workflow (.github/workflows/docker-image.yml):

  Triggers
    push main              → :main + :sha-<shortsha>
    tags v*.*.*            → :X.Y.Z + :X.Y + :X + :latest
    pull_request           → build-only, no push (sanity check)
    workflow_dispatch      → manual; 'tag' input publishes an
                              additional custom tag (e.g. aisix-e2e)

  Permissions               contents: read, packages: write
  Cache                     type=gha (GitHub Actions cache backend)
  Build                     docker/build-push-action@v6 with metadata
                            action@v5 for OCI labels + tag matrix

Image destination is ghcr.io/moonming/ai-gateway (same GHCR namespace
as this repo). Downstream orgs republishing under their own namespace
can do so in their own workflows via `docker pull` + `docker push`.

No runtime code changes.
Copilot AI review requested due to automatic review settings April 23, 2026 04:19

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds container build + publishing automation so downstream systems can consume aisix as a pre-built GHCR image instead of building from source.

Changes:

  • Introduces a multi-stage Dockerfile to build the aisix release binary and run it as a non-root user with tini.
  • Adds a GitHub Actions workflow to build (PRs) and publish (main/tags/manual) the image to GHCR with a tag/label strategy.
  • Adds a .dockerignore to reduce Docker build context noise.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

File Description
Dockerfile Multi-stage Rust build (builder + slim runtime) for the aisix binary.
.github/workflows/docker-image.yml Build/push workflow using buildx + metadata-action + GHA cache.
.dockerignore Excludes common non-image inputs from the Docker build context.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Dockerfile Outdated
Comment on lines +24 to +25
# Leverage Docker layer cache: copy manifests first so dependency
# compilation only re-runs when Cargo.toml / Cargo.lock change.

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment about leveraging layer cache is currently inaccurate: this layer copies crates/ before the build, so any source change will invalidate the dependency build cache. Consider copying only manifests first (workspace + per-crate Cargo.toml), doing a cargo fetch/dummy build to cache deps, then copying full sources (or update the comment if you intentionally accept full invalidation).

Suggested change
# Leverage Docker layer cache: copy manifests first so dependency
# compilation only re-runs when Cargo.toml / Cargo.lock change.
# Copy top-level manifests and then the full workspace sources.
# Because `crates/` is copied before the build, changes under `crates/`
# will invalidate the subsequent cargo build layer as well.

Copilot uses AI. Check for mistakes.
Comment thread Dockerfile Outdated
# The release build is what we ship.
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/src/target \
cargo build --release --bin aisix \

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reproducible container builds in CI, it’s safer to add --locked to the cargo build invocation so the build fails if Cargo.lock is out of date instead of silently resolving new versions during the image build.

Suggested change
cargo build --release --bin aisix \
cargo build --locked --release --bin aisix \

Copilot uses AI. Check for mistakes.
Comment thread Dockerfile
Comment on lines +38 to +52
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates tini \
&& rm -rf /var/lib/apt/lists/* \
&& useradd --system --uid 10001 --no-create-home --shell /usr/sbin/nologin aisix

COPY --from=builder /usr/local/bin/aisix /usr/local/bin/aisix

# Proxy + admin listeners from config.example.yaml.
EXPOSE 3000 3001

USER aisix

# tini forwards signals cleanly to the aisix process.
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/aisix"]
CMD ["--config", "/etc/aisix/config.yaml"]

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The image runs as a non-root user and the default CMD points at /etc/aisix/config.yaml, but the Dockerfile never creates /etc/aisix (or /etc/aisix/tls). Creating these directories at build time (and ensuring they’re readable by uid 10001) makes bind-mounting a config directory and adding TLS certs much more reliable.

Copilot uses AI. Check for mistakes.
Comment thread Dockerfile Outdated
Comment on lines +30 to +32
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/src/target \
cargo build --release --bin aisix \

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RUN --mount=type=cache requires BuildKit; without an explicit Dockerfile frontend directive, local docker build runs with BuildKit disabled will fail. Consider adding a # syntax=docker/dockerfile:1.x header (and/or documenting the BuildKit requirement) to make this more robust for local users.

Copilot uses AI. Check for mistakes.
Comment on lines +66 to +74
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha,prefix=sha-,format=short
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
type=raw,value=${{ github.event.inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag != '' }}

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Workflow comment/tag strategy says tag pushes produce only :X.Y.Z, :X.Y, :X, :latest, but docker/metadata-action is also configured to always add a sha-<short> tag. Either update the documented strategy comment/PR description or gate the sha tag to branch pushes so releases match the stated tags.

Copilot uses AI. Check for mistakes.
Comment thread .dockerignore Outdated
Comment on lines +4 to +6
ui/node_modules
ui/.next
ui/dist

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Docker build only copies Cargo* + crates/, so keeping ui/ sources in the build context adds transfer time without affecting the image. Consider ignoring ui/ entirely (or at least ui/src) to speed up CI builds and reduce cache churn.

Suggested change
ui/node_modules
ui/.next
ui/dist
ui

Copilot uses AI. Check for mistakes.
- Dockerfile: add `# syntax=docker/dockerfile:1.7` so BuildKit cache
  mounts are available on older `docker build` invocations that don't
  enable BuildKit by default.
- Dockerfile: pass `--locked` to `cargo build --release` so a stale
  `Cargo.lock` fails CI fast instead of silently resolving new deps
  in the image.
- Dockerfile: create `/etc/aisix/{,tls}` and `/var/lib/aisix` at build
  time, owned by the aisix user (uid 10001). Makes bind-mounting
  config/TLS directories ergonomic without post-install chown.
- Dockerfile: clarify the layer-cache / BuildKit comment — the cache
  mounts carry `target/` + `$CARGO_HOME/registry`, so source changes
  still reuse compiled dependencies; the extra manifests-only warm
  stage is a diminishing-return win for this workspace.
- .dockerignore: exclude `ui/` entirely. The Dockerfile only copies
  `Cargo.*` + `crates/`, so shipping ~50 MB of `ui/node_modules`
  (or even just the ui sources) into the build context is wasted
  transfer time.
- docker-image.yml: clarify tag-strategy comment — `:sha-<shortsha>`
  is always appended regardless of trigger; prior comment read as if
  it only applied to main-branch pushes.
@moonming

Copy link
Copy Markdown
Collaborator Author

Review response + CI status

Addressed Copilot review comments in 3c60894

# Comment Fix
1 Dockerfile: add # syntax=docker/dockerfile:1.7 for BuildKit cache mounts on older clients ✅ Added header
2 Dockerfile: --locked on cargo build ✅ Now cargo build --locked --release --bin aisix
3 Dockerfile: create /etc/aisix{,/tls} + /var/lib/aisix owned by aisix uid ✅ Added to the runtime-stage RUN
4 Dockerfile: clarify the layer-cache comment — source changes DO invalidate the COPY crates layer ✅ Comment now explains that BuildKit cache mounts on target/ + $CARGO_HOME/registry carry compiled deps across builds; an extra manifests-only warm stage is a diminishing-return win for this workspace (cargo workspaces don't support the manifest-only copy trick cleanly; would need a per-crate COPY Cargo.toml dance)
5 Workflow: tag-strategy comment said :sha-<shortsha> only on main; it's actually always appended ✅ Comment corrected
6 .dockerignore: exclude ui/ entirely (build context only needs Cargo + crates) ✅ Added ui

About the two failing checks

Both build ui and rust unit + coverage failed at the artifact upload step with:

##[error]Failed to CreateArtifact: Artifact storage quota has been hit.
Unable to upload any new artifacts. Usage is recalculated every 6-12 hours.

The tests themselves passed cleanly — e.g. rust unit ended with test result: ok. 57 passed; 0 failed. The failure is a repo-level Actions storage quota issue, unrelated to this PR's changes (Dockerfile + workflow YAML + .dockerignore cannot cause Rust / Node build artifacts to hit quota).

Two unrelated workarounds that may help the repo going forward:

  • Set actions/upload-artifact@v4 with retention-days: 3 (or 7) to cycle out coverage + ui-dist artifacts faster.
  • Purge old artifacts via the Settings → Actions → Storage page.

The new docker-image workflow this PR adds passed its PR-trigger build job (exit code 0, validates the Dockerfile without publishing).

Ready to merge

From my side the PR is ready. Once merged to main, the first push will publish ghcr.io/moonming/ai-gateway:main + :sha-<shortsha> automatically. For the aisix.cloud E2E suite I'll need the :aisix-e2e tag — I can trigger that via workflow_dispatch with tag=aisix-e2e after merge.

@moonming
moonming merged commit 2a40ee7 into main Apr 23, 2026
5 of 7 checks passed
@moonming
moonming deleted the feat/docker-image-publish branch April 23, 2026 07:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants