> ## Documentation Index
> Fetch the complete documentation index at: https://tyk.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Plugin Compiler

> Learn about the opt-in next-generation Go plugin compiler image, published alongside the existing plugin compiler

## Introduction

The next-generation plugin compiler (NG) is a second build of the [Tyk Plugin Compiler](/docs/api-management/plugins/golang#plugin-compiler) image. It is published from the same repositories with a `-ng` suffix on the tag, and takes the same arguments.

NG is opt-in. Both compilers are maintained in parallel, and nothing about the existing compiler changes.

If you are new to building Go plugins, start with [Golang Plugins](/docs/api-management/plugins/golang). This page describes only what NG adds and how it differs.

<Note>
  NG images are published for released Gateway versions, starting with 5.15.0 and the 5.13.2 LTS. The existing plugin compiler is still built and published for the same versions, so you can stay on it.
</Note>

## Why use it

A Go plugin must be built with the same toolchain, build tags and build flags as the Gateway that loads it. NG keeps that contract exactly, and changes what surrounds it.

### A much smaller vulnerability surface

NG is built on a Docker Hardened Image. Its C toolchain is provisioned by Docker rather than installed at build time, and Docker publishes assessments for the packages it ships. Scanned with Tyk's published VEX feed applied, the NG image reports **0 Critical and 0 High** findings.

This applies to the FIPS image too, so a FIPS build no longer means accepting a larger set of findings. See [Scanning the image](#scanning-the-image-for-vulnerabilities) to reproduce it yourself.

### Native ARM64 support

The image is published for `linux/arm64` as well as `linux/amd64`. On Apple silicon or AWS Graviton it runs on the host architecture, not through emulation. Builds are therefore quicker, and they behave the same as they do on an x86 machine.

### More target architectures from one image

A single NG image cross-compiles, so you no longer need a separate build path per architecture. The Community image builds for `linux/amd64`, `linux/arm64` and `linux/s390x`. The EE and FIPS images build for `linux/amd64` and `linux/arm64`, because no `s390x` Gateway is published for those editions.

### Problems surface at build time, not at load time

After a build, NG inspects the plugin it produced and checks the architecture, Go version, edition, FIPS mode and glibc requirements. If any of them do not match what the Gateway expects, the build fails with a message explaining why. The plugin therefore does not fail later, when the Gateway tries to load it.

### Compatibility with older Linux distributions is preserved

Plugins are linked against glibc 2.17. They keep running on the same range of systems as before, even though the image itself is built on a modern base.

## Choosing between the two compilers

|                                      | Existing plugin compiler                                                          | NG plugin compiler                                                                           |
| ------------------------------------ | --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| Image tag                            | `tykio/tyk-plugin-compiler:<version>`                                             | Same repository, `-ng` suffix: `tykio/tyk-plugin-compiler:<version>-ng`                      |
| Published for                        | Released Gateway versions                                                         | Gateway version 5.15+ and 5.13.x (LTS)                                                       |
| Invocation                           | `/build.sh <plugin_name> [plugin_id] [GOOS] [GOARCH]`, source at `/plugin-source` | Identical                                                                                    |
| Target architectures                 | `linux/amd64`, `linux/arm64`                                                      | `linux/amd64`, `linux/arm64`, `linux/s390x` (CE); `linux/amd64`, `linux/arm64` (EE and FIPS) |
| Host architectures the image runs on | `linux/amd64`                                                                     | `linux/amd64`, `linux/arm64`                                                                 |
| glibc floor of the produced plugin   | Follows the image's base OS                                                       | Pinned by a glibc 2.17 link sysroot shipped in the image                                     |
| Post-build checks                    | None                                                                              | Architecture, Go version, edition tags, FIPS evidence, glibc ceiling, linked libraries       |
| `plugin_id` behaviour                | Rewrites the plugin `go.mod` module path and Go import paths                      | Only names the build directory; an existing `go.mod` is never rewritten                      |
| Base image                           | `tykio/golang-cross`                                                              | Docker Hardened Images customization with a Docker-provisioned toolchain                     |
| Gateway test binary inside the image | Present (`/usr/local/bin/tyk`)                                                    | Not present in published images                                                              |

Use the existing compiler if you are building plugins for a released Gateway version and your build works today.

Try NG in these cases:

* You want to build for more than one architecture from one image.
* You want your builds to run natively on an ARM64 machine.
* You want the build to fail early rather than at load time.
* You need the vulnerability posture of the hardened base.

The interface is identical, so trying NG is usually just a change of image tag.

## Image variants

NG is published from the same three Docker Hub repositories as the existing compiler:

| Edition           | Image                                         | Default `EDITION` |
| ----------------- | --------------------------------------------- | ----------------- |
| Community         | `tykio/tyk-plugin-compiler:<version>-ng`      | `ce`              |
| Enterprise        | `tykio/tyk-plugin-compiler-ee:<version>-ng`   | `ee`              |
| Enterprise + FIPS | `tykio/tyk-plugin-compiler-fips:<version>-ng` | `ee-fips`         |

The edition is baked into each image, together with the build tags, `GOFIPS140` / `GOEXPERIMENT` settings and the list of architectures that edition's Gateway is published for. You do not need to set `EDITION` yourself. You cannot use the CE image to produce an EE or FIPS plugin, and the build fails with an explicit message if you try.

Match the image to the Gateway you will load the plugin into. An `ee` plugin will not load into a CE Gateway, and a FIPS Gateway will reject a plugin built without FIPS crypto.

<Note>
  The hardened base used by NG is a supply-chain hardening measure. It is not in itself a FIPS compliance claim. FIPS plugin crypto comes from the FIPS Gateway's Go toolchain and the edition settings baked into the FIPS image.
</Note>

## Quick start

Mount your plugin source at `/plugin-source` and pass the output name. The entrypoint is `/build.sh`, the working directory is `/go/src/github.com/TykTechnologies/tyk`, and the container runs as root — exactly as with the existing compiler:

```bash theme={null}
docker run --rm -v "$(pwd):/plugin-source" \
  tykio/tyk-plugin-compiler:v5.15.0-ng plugin.so
```

The built plugin is moved back into `/plugin-source`, so it appears in your working directory. The output file name is `{plugin_name%.*}_{GATEWAY_VERSION}_{GOOS}_{GOARCH}.so` — for the example above, `plugin_v5.15.0_linux_amd64.so`.

For Enterprise or FIPS plugins, change the repository:

```bash theme={null}
docker run --rm -v "$(pwd):/plugin-source" \
  tykio/tyk-plugin-compiler-ee:v5.15.0-ng plugin.so

docker run --rm -v "$(pwd):/plugin-source" \
  tykio/tyk-plugin-compiler-fips:v5.15.0-ng plugin.so
```

### Positional arguments

The four positional arguments are unchanged from the existing compiler:

1. `plugin_name` — the output name, for example `vendor-plugin.so`. It must be a bare file name, not a path.
2. `plugin_id` — optional. It selects the build directory inside the container, and supplies the generated module name when your plugin has no `go.mod`.
3. `GOOS` — optional override.
4. `GOARCH` — optional override.

```bash theme={null}
docker run --rm -v "$(pwd):/plugin-source" \
  tykio/tyk-plugin-compiler:v5.15.0-ng plugin.so "$(date +%s)"
```

<Note>
  NG never rewrites an existing plugin `go.mod` module path or the import paths in your Go sources. The existing compiler rewrites both when `plugin_id` is supplied.

  If your workflow relied on that rewrite to build the same plugin twice into one Gateway, set the module path in your own `go.mod` instead.
</Note>

### Environment variables

| Variable              | Effect                                                                                                                                                                                                                                                                                                                                |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `VALIDATE`            | `VALIDATE=0` skips post-build validation. Any other value, or unset, runs it.                                                                                                                                                                                                                                                         |
| `EDITION`             | `ce`, `ee` or `ee-fips`. Defaults to the edition baked into the image. A higher-privilege value than the image supports fails the build immediately. A lower one, for example `ce` on an EE or FIPS image, compiles but produces a plugin that fails to load in that Gateway. `FIPS=1` is accepted as an alias for `EDITION=ee-fips`. |
| `GOOS`, `GOARCH`      | Alternative to the third and fourth positional arguments.                                                                                                                                                                                                                                                                             |
| `BUILD_TAG`           | Extra build tags, appended to the `goplugin` tag and any edition tag.                                                                                                                                                                                                                                                                 |
| `GO_GET`              | `GO_GET=1` runs `go get github.com/TykTechnologies/tyk@<gateway sha>` before building.                                                                                                                                                                                                                                                |
| `GO_TIDY`             | `GO_TIDY=1` runs `go mod tidy` before building.                                                                                                                                                                                                                                                                                       |
| `DEBUG`               | `DEBUG=1` turns on shell tracing and prints a diff of the prepared build directory.                                                                                                                                                                                                                                                   |
| `PLUGIN_SOURCE_PATH`  | Source mount point. Defaults to `/plugin-source`.                                                                                                                                                                                                                                                                                     |
| `PLUGIN_BUILD_PATH`   | Build directory inside the container. Defaults to a directory derived from `plugin_name` and `plugin_id`.                                                                                                                                                                                                                             |
| `PLUGIN_TRIMPATH`     | Overrides the `-trimpath` setting derived from the Gateway. Only needed for old Gateway releases whose binaries do not record the flag.                                                                                                                                                                                               |
| `PLUGIN_BUILD_METHOD` | `auto` (default), `workspace`, `replace` or `gopath`. `auto` picks a Go workspace on Go 1.18 and newer; the other methods exist for older Gateway releases.                                                                                                                                                                           |

The glibc target is a property of the image rather than a runtime setting. Each image ships exactly one sysroot set, and published NG images ship glibc 2.17.

## Cross-compiling

Pass the target as the third and fourth positional arguments:

```bash theme={null}
docker run --rm -v "$(pwd):/plugin-source" \
  tykio/tyk-plugin-compiler:v5.15.0-ng plugin.so "" linux arm64
```

Or as environment variables:

```bash theme={null}
docker run --rm -e GOOS=linux -e GOARCH=arm64 -v "$(pwd):/plugin-source" \
  tykio/tyk-plugin-compiler:v5.15.0-ng plugin.so
```

Both forms produce `plugin_v5.15.0_linux_arm64.so`. `s390x` works the same way with the CE image:

```bash theme={null}
docker run --rm -e GOARCH=s390x -v "$(pwd):/plugin-source" \
  tykio/tyk-plugin-compiler:v5.15.0-ng plugin.so
```

The set of allowed target architectures is checked against the architectures the chosen edition's Gateway is published for. Asking the EE or FIPS image for `s390x` fails. The message lists the architectures that edition supports. No such Gateway exists to load the plugin.

The compiler images run natively on both `linux/amd64` and `linux/arm64`, so on Apple silicon or Graviton the build is not emulated. The target architecture is still a separate choice from the host: set `GOARCH` explicitly whenever you want something other than the machine you are on.

## Post-build validation

After the build, NG inspects the produced `.so`. This happens before the artifact is moved back to `/plugin-source`. NG checks that:

* The file is a 64-bit ELF shared object built with `-buildmode=plugin`.
* The ELF machine type matches the requested `GOARCH`.
* The Go toolchain recorded in the plugin matches the Gateway's Go version.
* For `ee` and `ee-fips`, the `ee` build tag is present.
* For `ee-fips`, the binary carries FIPS crypto evidence.
* The linked libraries are ones the Gateway provides. A plugin linked against musl or `libpython` is rejected, because the Gateway image ships neither.
* The highest required `GLIBC_x.y` symbol version is not newer than the image's glibc target.

It also warns, without failing, when the `github.com/TykTechnologies/tyk` revision the plugin links does not match the Gateway revision.

Successful checks are printed as `[ok]` lines — for example, a line reporting the architecture and a line reporting `GLIBC ceiling: 2.17 <= 2.17` — and the run ends with `== validation OK: <file> ==`.

### Interpreting a failure

Failures are printed as `ERROR (plugin validation): ...` and stop the build with a non-zero exit code. The common ones are:

| Message                                                                         | What it means                                                                                                                                                                         |
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Go toolchain mismatch: plugin=..., gateway=...`                                | The plugin was built with a different Go version than the Gateway. Use the compiler image whose tag matches your Gateway release.                                                     |
| `GOARCH mismatch: built ..., expected ...`                                      | The produced object is for a different architecture than requested. Check that you have not set `GOARCH` in two places at once.                                                       |
| `plugin requires GLIBC_x.y which is NEWER than the supported target GLIBC_2.17` | Your C code, or a cgo dependency, uses a symbol that only exists in a newer glibc. Either avoid that function, or accept that the plugin needs a newer runtime than the pinned floor. |
| `EDITION=ee ... lacks 'ee'`                                                     | The plugin does not carry the enterprise build tag. You are probably using the CE image for an EE Gateway.                                                                            |
| `EDITION=ee-fips but the plugin shows NO FIPS crypto`                           | Build with the FIPS image.                                                                                                                                                            |
| `plugin links musl libc` / `plugin links libpython...`                          | The plugin depends on a library the Gateway image does not ship, and would fail to load at runtime.                                                                                   |

Each of these would otherwise surface as an opaque `plugin was built with a different version of package ...` error when the Gateway loads the plugin. If you need the artifact regardless — to inspect it, for example — rerun with `-e VALIDATE=0`.

## Scanning the image for vulnerabilities

NG is built on a Docker Hardened Images base, and Docker publishes affectedness decisions (VEX statements) for the packages it provisions. Those decisions are what make the difference between a raw scan and the reported result.

Scanning without VEX reports findings in kernel headers (`linux-libc-dev`), `perl`, `busybox` and `libcurl4t64` — packages Docker has assessed as not affecting the image. With Tyk's VEX repository configured, the same scan reports **0 Critical and 0 High**.

### Configuring the VEX repository

Create the Trivy VEX repository configuration at `$XDG_DATA_HOME/.trivy/vex/repository.yaml`, or at `$HOME/.trivy/vex/repository.yaml` when `XDG_DATA_HOME` is not set:

```yaml theme={null}
repositories:
  - name: tyk-dhi
    url: https://tyktechnologies.github.io/tyk-vex-records
    enabled: true
```

Download the repository, then scan with `--vex repo`:

```bash theme={null}
trivy vex repo download

trivy image --scanners vuln --severity HIGH,CRITICAL \
  --vex repo --show-suppressed \
  tykio/tyk-plugin-compiler-ee:v5.15.0-ng
```

<Note>
  Three things are easy to get wrong here:

  1. The `url` is the site root. Sub-paths such as `/tyk` or `/dhi` do not exist and return 404.
  2. If you have other VEX repositories configured, list `tyk-dhi` first. A repository that merely lists a package takes precedence over lower-priority repositories for that package. This applies even when it carries no statement for the CVE in question.
  3. Pass `--show-suppressed`. Without it, suppressed findings disappear from the report with no indication that anything was suppressed.
</Note>

## Things to know before you switch

* Each image ships one glibc sysroot set — 2.17 in published images. There is no runtime switch to a different glibc target.
* The edition is fixed per image. `EDITION=ee` or `EDITION=ee-fips` fails in an image that was not built for that edition.
* `linux/s390x` is available for the Community edition only, because EE and FIPS Gateways are not published for that architecture.
* Published NG images deliberately contain no Gateway executable, so you cannot run `tyk plugin load` inside the compiler container. Load and exercise your plugin in a Gateway container instead.
* `plugin_id` no longer rewrites your module path or import paths. See [Positional arguments](#positional-arguments).

## Relationship to the existing plugin compiler

The compilers are maintained together. NG does not replace the existing plugin compiler, and existing users need change nothing. The existing images continue to be built and published for released Gateway versions, from the same repositories and tags as before.

NG is opt-in and interface-compatible. Its entrypoint, working directory, positional arguments, environment variables, source mount point and output naming are the same. Moving a build to NG is normally a tag change, and moving back is the same change in reverse.

NG is available alongside released Gateway versions, from 5.15.0 and the 5.13.2 LTS. No date has been set for it to become the default. Use NG where its native ARM64 support, cross-compilation, validation or hardened base are useful. The existing compiler remains fully supported.

## Migrating from the existing compiler

This section is for anyone who already builds Go plugins with `tykio/tyk-plugin-compiler` (or the `-ee` / `-fips` repositories) and wants to move to NG. You do not have to migrate.

### What changes

The image tag. NG is published from the same three repositories with a `-ng` suffix:

| Existing                                   | NG                                            |
| ------------------------------------------ | --------------------------------------------- |
| `tykio/tyk-plugin-compiler:<version>`      | `tykio/tyk-plugin-compiler:<version>-ng`      |
| `tykio/tyk-plugin-compiler-ee:<version>`   | `tykio/tyk-plugin-compiler-ee:<version>-ng`   |
| `tykio/tyk-plugin-compiler-fips:<version>` | `tykio/tyk-plugin-compiler-fips:<version>-ng` |

NG is published for released Gateway versions, starting with `v5.15.0-ng` and `v5.13.2-ng` (2026 LTS). There is no NG compiler for Gateway v5.14.

```diff theme={null}
 docker run --rm -v "$(pwd):/plugin-source" \
-  tykio/tyk-plugin-compiler-ee:v5.15.0 plugin.so
+  tykio/tyk-plugin-compiler-ee:v5.15.0-ng plugin.so
```

### What does not change

* Entrypoint `/build.sh` and working directory `/go/src/github.com/TykTechnologies/tyk`.
* The container runs as root.
* Plugin source is mounted at `/plugin-source`, and the built `.so` is moved back there.
* Positional arguments: `plugin_name`, optional `plugin_id`, optional `GOOS`, optional `GOARCH`.
* Environment variables you already use: `GOOS`, `GOARCH`, `BUILD_TAG`, `GO_GET`, `GO_TIDY`, `DEBUG`, `PLUGIN_SOURCE_PATH`, `PLUGIN_BUILD_PATH`.
* Output naming: `{plugin_name%.*}_{GATEWAY_VERSION}_{GOOS}_{GOARCH}.so`.

### One behavioural difference to check first

NG never rewrites an existing plugin `go.mod` module path, and never rewrites import paths in your Go sources. The existing compiler does both when `plugin_id` is supplied. In NG, `plugin_id` only names the build directory, and supplies the generated module name when your plugin has no `go.mod` at all.

If you pass a `plugin_id` only to isolate the build directory, nothing changes for you. The same applies if you pass it to generate a module name when your plugin has no `go.mod`. If you relied on the module-path rewrite, set distinct module paths in your own `go.mod` files instead. That rewrite let you load two builds of the same plugin into one Gateway.

### What to check after the first NG build

1. **Validation output.** NG validates the artifact before it is moved back to `/plugin-source`. Confirm the run ends with `== validation OK: <file> ==` and read the `[ok]` lines.

   A failure is a real incompatibility rather than a compiler bug — the same problem would otherwise appear as `plugin was built with a different version of package ...` when the Gateway loads the plugin. `VALIDATE=0` skips validation if you need the artifact anyway.
2. **glibc floor.** The validation output reports the highest required GLIBC symbol version. It also reports the ceiling it is checked against, which is 2.17 in published NG images.

   If your plugin fails this check, a cgo dependency is using a symbol newer than the pinned floor. That plugin would have needed a newer runtime glibc than the pinned target.
3. **FIPS mode.** With the FIPS image, confirm the validator reports a `FIPS:` line. `EDITION=ee-fips but the plugin shows NO FIPS crypto` means the artifact would be rejected by a FIPS Gateway.
4. **Edition tags.** With the EE or FIPS image, confirm the `edition: 'ee' build tag present` line.
5. **Architecture.** If you cross-compile, confirm the reported ELF machine type matches your target. The compiler image runs natively on both `linux/amd64` and `linux/arm64`. Set `GOARCH` explicitly whenever your target differs from the machine you build on.
6. **Load the plugin.** Published NG images do not contain a Gateway binary. Run the `tyk plugin load` check against a Gateway container, not inside the compiler container.

### Rolling back

Drop the `-ng` suffix and rerun. Nothing else in your build needs to change, and the existing compiler continues to be published for released Gateway versions:

```bash theme={null}
docker run --rm -v "$(pwd):/plugin-source" \
  tykio/tyk-plugin-compiler-ee:v5.15.0 plugin.so
```

<Note>
  Rebuild the plugin after rolling back rather than reusing an NG artifact, so that the plugin and the toolchain that produced it stay consistent.
</Note>
