Skip to content

RELEASE.md

This document describes the structural procedures required to compile, verify, and release new production versions of the native Nginx module.

All build engineering is centralized inside the Go utility engine make_pkgs.go and driven automatically via GitHub Actions pipelines. Once the target matrix passes all integration testing checks, release artifacts are synchronized to the AWS S3 staging container (package-build-artifacts) for final deployment orchestration inside Voltron.


Upstream Version Monitoring

An automated tracking microservice executes on a daily cron loop to inspect upstream releases across nginx.org (Open Source) and nginx.com (Nginx Plus). * True upstream updates automatically generate a localized notification alert sent directly to your team Slack channel. * For deeper architecture details regarding the tracking runner, see monitor-link-svc. * We actively compile and deliver a dedicated native module iteration matching every formal Nginx release line, including minor point patches.


Pre-Release Phase: Scope Definition

Before updating target lines or triggering automated CI/CD builds, you must define the explicit scope of your incremental release. Because our modern infrastructure couples target tracking and environmental execution dependencies, your release scope dictates the behavior of the shouldBuild routing hook inside make_pkgs.go.


Incremental Release Path (Routine Version Patches & New OS Support)

Whether you are supporting a brand new upstream Nginx/Plus patch or spinning up support for a newly released OS distribution version, releases are always handled incrementally to limit resource consumption and speed up delivery times.

  1. Update Target Versions (If an upstream Nginx patch): Open make_pkgs.go and locate the vers initialization slice inside the work() driver function. Insert a new pkgVer structure tracking the fresh version target directly at the top of the collection, keeping the structural components in descending chronological order:
vers := []pkgVer{
    {"1.31.2", "", lRelease}, // Explicit upstream patch added here
    {"1.30.3", "", lRelease},
}
  • For Nginx Plus targets: Append the commercial tag identity explicitly to the second string parameter (e.g., {"1.29.8", "37.0", lRelease}).
  • Review the structural dists array configuration rows declared immediately below. Stretch the mainline, stable, or commercial boundary parameters (mainL, stableL, or plusL) across the active host distributions to allow coverage for your new versions (mVer, sVer, pVer).

  • Configure Distro Definitions (If adding a new OS target): If your incremental release is meant to bring up a new OS version, append its profile row to the dists slice array configuration inside make_pkgs.go:

// Definition alignment: Name, CodeName, Extension, MainlineRange, StableRange, PlusRange, DistroList, ArchitectureType
{"ubuntu", "resolute", DEB, vr{"1.31.0", mVer}, vr{"1.30.1", sVer}, vr{"1.29.8", pVer}, vl{"1.28.3"}, GLIBC},
  • Ensure specific distribution dependencies or explicit package manager abstractions (such as virtual interfaces like nginx-abi or pipe-joined platform operators) are cleanly assigned rows inside the depsNgx hash mapping structure.
  • Verify matching semantic string definitions are defined in the ubuntuR container resolver map (e.g., "ubuntu:resolute": "ubuntu:26.04").

  • Isolate Compilation via shouldBuild: This is the most crucial step. To keep the build strictly incremental, update the conditional gating constraints inside the shouldBuild function. Explicitly call out only your newly introduced version lines, safely dropping processing loops for all historical iterations:

Example 1: Scoping to explicit Nginx engine patches

func shouldBuild(v pkgVer, di dist, mVer, sVer string, bl buildLines) bool {
    if !bl.Any() {
        return false
    }

    // Clamp compilation strictly to the new targeted incremental lines
    if v.NgxVer == "1.30.3" || v.NgxVer == "1.31.2" {
        return true
    }

    return false 
}

Example 2: Scoping to a specific OS release line (e.g., an updated Ubuntu target) If you are dropping a release targeted only at a specific OS version update (like a fresh Ubuntu point release) without triggering an entire historical cascade, restrict by the distribution codename alongside the version boundaries:

func shouldBuild(v pkgVer, di dist, mVer, sVer string, bl buildLines) bool {
    if !bl.Any() {
        return false
    }

    // Isolate the build exclusively to the new Ubuntu release (e.g. 'resolute') for the targeted engine versions
    if di.code == "resolute" && (v.NgxVer == "1.30.1" || v.NgxVer == "1.31.0") {    
        return true
    }

    return false 
}
  1. Do NOT update the package tracking file (VERSION). Keeping this module metadata value matching the concurrent release version maintains clean tracking configurations. Record your targeted patch details as bullet points inside the existing release block in CHANGELOG.md.

The Dynamic Test Matrix

Rather than using rigid, hardcoded configuration files, our pipeline computes execution targets dynamically at runtime. When the GitHub Actions pipeline boots, it queries the Go tool using a special generation flag:

bash go run make_pkgs.go -generate-matrix

How the Matrix Evaluates Your Changes

The Go builder evaluates the active vers block, filters them through your active boundaries in the dists block, and passes them to the shouldBuild function.

For every unique variation where shouldBuild evaluates to true, the tool generates a localized JSON object tracking five core variables. A compiled output array resembles the following layout:

{
  "test_matrix": [
    {
      "distro": "ubuntu",
      "os_version": "24.04",
      "branch": "mainline",
      "nginx_version": "1.31.2",
      "compose_ext": "ubuntu"
    },
    {
      "distro": "alpine",
      "os_version": "3.23",
      "branch": "stable",
      "nginx_version": "1.30.3",
      "compose_ext": "alpine"
    }
  ]
}

This JSON object is outputted directly to the GitHub Actions setup job outputs. The downstream test workflow job ingests this matrix array via the strategy.matrix.include directive, seamlessly spinning up perfectly matched, isolated test containers for only the new packages you are releasing.


GitHub Actions Architecture Workflow

Our continuous integration design runs a singular pipeline file (.github/workflows/build.yml) that replaces old individual per-OS shell scripts with parallel concurrent runner jobs and automated verification steps.

Milestone 1: Setup Matrix Generation

The workflow boots an initial context engine and clones upstream asset code from nginx/nginx to compute tracking flags. It fires the Go tool to parse the matrix metadata configuration constraints:

bash go run make_pkgs.go -generate-matrix

This prints a clean JSON block (test_matrix), which is then injected directly into downstream workflow variables to dynamically spin up runners.

Milestone 2: Highly Parallelized Compilation

The platform provisions decoupled processing tracks split via GitHub concurrency strategies: * Standard Operating Systems: Spun up across 6 automated glibc/musl compiler platforms processing unique partition indexes concurrently (-groupN 6 -groupI [0-5]). * ARM64 Architectures: Routed onto 2 dedicated, isolated self-hosted ARM64 hardware runner environments (-groupN 2 -groupI [0-1]).

Each worker invokes the central compilation execution layer:

PATH=$PATH:~/go/bin: go run make_pkgs.go ${CI} -groupN ${{ matrix.groupN }} -groupI ${{ matrix.groupI }} -j ${{ matrix.j }}

Milestone 3: Central Aggregation and Asset Signing

Upon parallel job completions, an autonomous aggregate runner pulls down the isolated execution footprints, joins divergent paths together via rsync structures under out/dist, and safely draws down production code signing signatures from AWS Secrets Manager to generate signed indexes via make repo.

Milestone 4: Environmental Integration Validation

For each environment returned in the dynamic matrix list, a dedicated test job spins up. If a valid system target is found, it evaluates verification steps: 1. It tries to pull down a pre-cached container matching the target deployment framework from the ECR registry. 2. On a cache miss, it handles custom localized processing utilizing multi-stage docker configurations:

docker compose -f install_testing/docker-compose.yml \
               -f install_testing/docker-compose.${{ matrix.compose_ext }}.yml build web \
               --build-arg OS_VERSION="${{ matrix.os_version }}" \
               --build-arg BRANCH="${{ matrix.branch }}" \
               --build-arg NGINX_VERSION="${{ matrix.nginx_version }}"
  1. The runner mounts the aggregate native package directly into the target environment container, boots live runtime services, and executes structural integration paths via ./install_testing/test.sh. This ensures full backward compatibility and system reliability prior to shipping the packages to final release bins.