Alpine Linux image with nginx latest with, TLSv1.3, 0-RTT, brotli, NJS, Cookie-Flag support. All built on the bleeding edge. Built on the edge, for the edge.
The supported automated build and publish path for this repository is GitHub Actions.
The release workflow lives in .github/workflows/image.yml and is recognized by GitHub Actions on the default branch.
Published images:
- Primary:
ghcr.io/woosungchoi/nginx-http3 - Compatibility mirror:
docker.io/<dockerhub-user>/docker-nginx-brotli
GitHub Actions remains the single source of truth for release images. Docker Hub autobuild is intentionally retired for this repository. When Docker Hub credentials are configured as GitHub Actions secrets, the same multi-arch image build is pushed to Docker Hub as a mirror in addition to GHCR.
The GitHub Actions image workflow publishes a single multi-arch manifest from one buildx --platform ... --push invocation. That keeps one authoritative build pipeline for release images and avoids the older Docker Hub autobuild branch-split behavior where tags could drift or lose a platform.
Legacy hooks/build and hooks/push files are kept as explicit no-ops so an accidental Docker Hub autobuild re-enable does not publish from an unexpected path.
GHCR: docker pull ghcr.io/woosungchoi/nginx-http3:latest
Docker Hub mirror: docker pull <dockerhub-user>/docker-nginx-brotli:latest
This is a base image like the default nginx image. It is meant to be used as a drop-in replacement for the nginx base image.
Best practice example Nginx configs are available in this repo. See nginx.conf and h3.nginx.conf.
Example:
# Base Nginx HTTP/2 Image
FROM ghcr.io/woosungchoi/nginx-http3:latest
# Copy your certs.
COPY localhost.key /etc/ssl/private/
COPY localhost.pem /etc/ssl/
# Copy your configs.
COPY nginx.conf /etc/nginx/
COPY h3.nginx.conf /etc/nginx/conf.d/NOTE: Please note that you need a valid CA signed certificate for the client to upgrade you to HTTP/2. Let's Encrypt is a option for getting a free valid CA signed certificate.
The workflow always logs in to GHCR and publishes these GHCR tags:
ghcr.io/woosungchoi/nginx-http3:latestghcr.io/woosungchoi/nginx-http3:<branch-or-tag>ghcr.io/woosungchoi/nginx-http3:<short-sha>
If both repository secrets below are configured, the workflow also logs in to Docker Hub and mirrors the same tags to the legacy Docker Hub repository name:
DOCKER_USERNAMEDOCKER_PASSWORD
Mirrored Docker Hub tags:
<DOCKER_USERNAME>/docker-nginx-brotli:latest<DOCKER_USERNAME>/docker-nginx-brotli:<branch-or-tag><DOCKER_USERNAME>/docker-nginx-brotli:<short-sha>
If either secret is missing, the workflow skips Docker Hub login and Docker Hub pushes entirely while continuing to publish to GHCR.
The Dockerfile pins NGINX_VERSION, PCRE_VERSION, and ZLIB_VERSION on purpose.
A scheduled GitHub Actions workflow now checks for upstream releases and opens a pull request when those pins need to move.
Key files:
scripts/update_versions.py- resolves the latest supported versions and rewrites the Dockerfile when needed.github/workflows/update-versions.yml- runs weekly on a schedule and on manual dispatch, then opens or updates a PR viapeter-evans/create-pull-request
- NGINX: latest stable release only, parsed from
https://nginx.org/download/- The updater looks for
nginx-X.Y.Z.tar.gzentries and keeps only versions where the minor numberYis even. - Example:
1.29.xis mainline,1.30.xis stable.
- The updater looks for
- PCRE2: latest stable GitHub release from
PCRE2Project/pcre2 - zlib: latest stable GitHub release from
madler/zlib
If the updater changes the Dockerfile, GitHub Actions commits those changes to a dedicated branch (ci/update-pinned-versions) and opens or refreshes a pull request against the default branch using the standard GITHUB_TOKEN.
Pull requests run the smoke-test workflow first. After that check passes, automated dependency PRs are handled as follows:
- Auto-merge: patch-level nginx updates within the same stable branch, PCRE2 updates, and zlib updates.
- Manual merge: nginx stable branch changes, for example
1.30.xto1.32.x.
This keeps routine dependency refreshes automatic while preserving human review for larger nginx stable-branch moves.
You can inspect what the updater would do without editing files:
python3 scripts/update_versions.py --dry-runTo fail when updates are available (useful for local checks):
python3 scripts/update_versions.py --check- HTTP/2 (with Server Push)
- BoringSSL (Google's flavor of OpenSSL)
- TLS 1.3 with 0-RTT support
- Brotli compression
- headers-more-nginx-module
- NJS
- nginx_cookie_flag_module
- PCRE latest with JIT compilation enabled
- zlib latest
- Alpine Linux (total size of 10 MB compressed)
host=domain.example.com # Replace your domain.
echo -e "GET / HTTP/1.1\r\nHost: $host\r\nConnection: close\r\n\r\n" > request.txt
openssl s_client -connect $host:443 -tls1_3 -sess_out session.pem -ign_eof < request.txt
openssl s_client -connect $host:443 -tls1_3 -sess_in session.pem -early_data request.txt

