GitHub
08/29/2026, 9:06 PMosctrl-frontend as a release Docker image
Problem
Every tagged release publishes osctrl-tls, osctrl-api, and osctrl-cli
to Docker Hub, but the React admin SPA has no release image. With
osctrl-admin now archived, operators have no prebuilt, shippable frontend
artifact — they must build the Vite bundle themselves and wire up nginx from
the example config. The dev stack (Dockerfile-dev-frontend) exists but is
unsuitable for releases (runs Node + air in-container, no production nginx
config, no TLS path).
Change
Add a fourth release image, osctrl-frontend, built and published alongside
the existing components on every tagged release. It is a plain nginx image
serving the pre-built Vite bundle and reverse-proxying /api/* to
osctrl-api:9002 — one process per container, no Node at runtime, no Go
binary. TLS is opt-in so the same image works behind an external terminator
or as a self-contained HTTPS endpoint.
New files
• deploy/cicd/docker/Dockerfile-osctrl-frontend — nginx:1.27-alpine base.
Stages both HTTP and TLS server blocks into /etc/nginx/conf.d-available/
(outside the active conf.d), copies the Vite bundle to
/usr/share/nginx/osctrl-frontend/, and runs an entrypoint that selects
the active config at startup. Exposes 80 and 443. ENV OSCTRL_FRONTEND_TLS=0.
• deploy/cicd/nginx/frontend.conf — HTTP-only server block on :80.
Long-cached /assets/ + /monaco/, SPA fallback, /api/ reverse-proxy,
full security headers re-stated per location (nginx add_header does not
inherit). Mirrors the dev-stack frontend-dev.conf and the example in
deploy/nginx/frontend.conf.
• deploy/cicd/nginx/frontend-tls.conf — HTTPS on :443 with certs at
/etc/ssl/osctrl/tls.{crt,key}, HTTP→HTTPS redirect on :80, HSTS added to
every header set. Same location structure as the HTTP variant.
• deploy/cicd/nginx/docker-entrypoint-frontend.sh — symlinks the chosen
config into /etc/nginx/conf.d/ based on `OSCTRL_FRONTEND_TLS`; fails
fast if TLS is enabled but certs are missing; execs
nginx -g 'daemon off;'.
.goreleaser.yml
• Two new dockers entries (amd64 + arm64) with ids: [] (no Go binary to
stage) and extra_files listing frontend/dist, both nginx configs, and
the entrypoint script.
• Two new docker_manifests entries (:<version> + :latest), gated by
skip_push: '{{ .IsSnapshot }}' like the other components.
.github/workflows/release.yml
• Added NODE_VERSION: "22" env.
• Added setup-node (with npm cache) + make frontend + a sanity check
that frontend/dist/index.html and /assets/ exist, before GoReleaser
runs. The bundle is produced once and copied into the image — the
Dockerfile does not run npm.
• Added frontend to the cosign verify loop so the new image is signed and
verified like `tls`/`api`/`cli`.
Usage
# Default — HTTP on :80, behind a TLS terminator
docker run -p 80:80 <org>/osctrl-frontend
# Self-contained TLS — HTTPS on :443, HTTP→HTTPS on :80
docker run -p 80:80 -p 443:443 \
-v /path/tls.crt/etc/ssl/osctrl/tls.crtro \
-v /path/tls.key/etc/ssl/osctrl/tls.keyro \
-e OSCTRL_FRONTEND_TLS=1 \
<org>/osctrl-frontend
jmpsec/osctrlGitHub
08/29/2026, 9:26 PM