Skip to content

Building

docker build --build-arg KONG_VERSION=2.8.5 -t kong-gateway:2.8.5 .
./tests/run.sh kong-gateway:2.8.5 2.8.5

tests/run.sh runs the milestones, and the pre-push gate (tests/smoke.sh) is one of them — run it alone when that is all you want to know.

On an arm64 workstation the base image is amd64-only, so build and test under emulation:

docker build --platform linux/amd64 --build-arg KONG_VERSION=2.8.5 -t kong-gateway:2.8.5 .
DOCKER_PLATFORM=linux/amd64 ./tests/run.sh kong-gateway:2.8.5 2.8.5

The base image is pinned by digest, not by tag — the same rule this repository asks of the deployments that run its images. The mapping lives in kong-base-digests.env:

2.8.5=sha256:a2b02d51…
3.9.3=sha256:ca71c559…

ARG KONG_DIGEST defaults to the entry for the default KONG_VERSION, so a bare docker build is pinned too; CI passes the entry for the line it is building, and tests/policy.sh checks the default still matches the file and that every line CI builds has an entry. Refreshing a digest is a deliberate act — a new digest is a different base image, and that belongs in a commit with a reason.

KONG_VERSION selects both the base image and the plugin set, so one tree builds several Kong majors. A second argument, OIDC_PROVIDER, selects the OIDC implementation — which is what makes the variant below possible.

Three images from one tree

Build OIDC Why it exists
KONG_VERSION=2.8.5 kong-oidc (Lua) Reproduces what runs today, bit for bit
KONG_VERSION=2.8.5 OIDC_PROVIDER=oidcify oidcify (Go) Same Kong, same jwt-keycloak, maintained OIDC
KONG_VERSION=3.9.3 oidcify (Go) kong-oidc cannot run on 3.x at all
docker build --build-arg KONG_VERSION=2.8.5 --build-arg OIDC_PROVIDER=oidcify \
  -t kong-gateway:2.8.5-oidcify .
./tests/e2e/run.sh kong-gateway:2.8.5-oidcify 2.8.5

The variant exists to separate two migrations that would otherwise arrive together: getting off an abandoned OIDC plugin, and jumping a Kong major. It does the first alone — same Kong 2.8.5, same jwt-keycloak, same kong-path-allow, one plugin swapped — so a rollback is a tag, not a replan. The end-to-end suite runs against all three.

Both Kong lines are open source — that is not what changes at 3.x

Kong Gateway is Apache-2.0 on both lines; nothing about the licence forces staying on 2.8.5. What ends is the prebuilt official image: Docker Hub's kong library has tags up to 3.9.3 and nothing for 3.10 or later. And 2.8.5 is not standing still by choice — it was released in June 2024 and has had no release since, while 3.9.3 was released in June 2026.

Kong 2.8 cannot start a Go plugin out of the box

It looks for the plugin-server protobuf definitions in /usr/local/kong/lib, and the image ships them in /usr/local/kong/include. Without a copy, Kong 2.8 fails at init with module load error: pluginsocket.proto — and then, once that is fixed, google/protobuf/descriptor.proto. The Dockerfile copies the whole include tree for the 2.x variant. It is a path bug in a line that will get no further releases, so it is worked around rather than waited on.

⚠️ The LuaRocks trap

The obvious Dockerfile does not work:

RUN luarocks install kong-oidc 1.1.0-0     # fails
Warning: Failed searching manifest: Failed loading manifest for https://luarocks.org:
  main function has more than 65536 constants
Error: No results matching query were found for Lua 5.1.

The LuaRocks client shipped inside older Kong images can no longer parse the luarocks.org manifest. The public index outgrew the Lua 5.1 limit on constants per chunk, so loading it aborts. Every lookup that goes through the manifest fails — the package you asked for, and each of its dependencies.

This is worth naming plainly: it is not a transient outage, and it does not get better. An old toolchain quietly loses the ability to reach the modern package index, and the first symptom is that a build which "should obviously work" does not.

The fix is to bypass the index and install from pinned rockspec URLs:

RUN luarocks install --deps-mode=none \
      "https://luarocks.org/manifests/seifchen/kong-path-allow-0.1-3.rockspec"

--deps-mode=none is required for the same reason: dependency resolution would go back through the manifest. So dependencies are installed explicitly, in order, and their correctness is checked at test time rather than at build time.

This is stricter than the naive form, not looser: every rock is pinned to one exact artefact, and a rock that moves upstream cannot move under the build.

Milestones

tests/smoke.sh answers one question: may this image be published? The milestone suite answers the wider one — how far along the plan this image is:

./tests/run.sh kong-gateway:2.8.5 2.8.5

Each file in tests/milestones/ holds one exit criterion. A milestone not reached yet is declared expect xfail: it fails on purpose and is reported as XFAIL, not as a failure. When it starts passing the runner goes red (XPASS) until the declaration is dropped and milestones.md updated, so that neither a normalised red nor an unrecorded green can sit there unnoticed.

Running it by hand

scripts/run-local.sh up

Starts the gateway with a real Keycloak behind it and leaves it running — see Running locally. The end-to-end suite starts the same stack and tears it down; this one stays out of the way.

Beyond the image

The smoke test and the milestones inspect an image. End-to-end runs the whole system — Keycloak, an upstream, and the image between them — and asserts what the plugins refuse.

What the smoke test checks

tests/smoke.sh runs before anything is published. It is deliberately not a healthcheck.

  • exactly one OIDC implementation — kong-oidc or oidcify, never both and never neither. An image carrying both would leave it to a configuration file to decide which one guards a route, with the abandoned one still a live code path. The gate also asks the image which one it declares and refuses a mismatch: a check that is told what to expect cannot catch a build that produced something else
  • rocks are installed — the shallowest check, and the least meaningful on its own
  • modules load — installing a rock proves a file was copied; requiring the module proves its dependencies actually resolved, which is precisely what --deps-mode=none does not verify at build time
  • PRIORITY and VERSION are declared — Kong 3.0 refuses to load a plugin without VERSION; Kong 2.x does not, which is exactly how a missing one goes unnoticed for years
  • Kong accepts the configuration with all plugins enabled
  • kong-path-allow is usable as an access control
  • on an oidcify image: that the binary answers Kong's schema query on this base image, and — on the 2.x variant — that the protobuf definitions sit where Kong 2.8 looks for them, without which Kong does not start at all

Handlers need a stub

Plugin handlers touch the kong global at module scope, so a bare require outside the Kong runtime fails for reasons that say nothing about the image. The test injects a minimal stub. That is the honest harness: it still proves the file parses and its requires resolve, without pretending to be a running gateway.

An authorization control is tested by what it refuses

kong-path-allow decides which paths are permitted. A plugin that loads but no longer blocks anything passes every healthcheck and every positive test. Any test added here for it must keep asserting the negative case.