Faster WSLProxy Deploys: Prebuilt Images, Build Cache — and Using AI So the Pipeline Work Actually Ships
Stop compiling OpenResty on every POP — prebuilt images, Buildx/GHA cache, DEPLOY_MODE=code for day-2, and an honest take on AI-assisted infra delivery
Workstation WSL Proxy (WSLProxy) — stop compiling OpenResty on every POP. Treat the binary tree as a cached artefact, keep day-2 deploys on the fast path, and admit why teams rarely ship this until an AI-assisted loop makes the cross-cutting work finishable. Deep dive: long technote. Product: /wsl-proxy · wslproxy.org.
bwalia/wslproxy-openresty, warm the layers with Buildx/GHA cache, extract on bare metal without wiping live conf/html/logs, and prefer DEPLOY_MODE=code for routine work. Timings are environment-dependent — the shape of the win is compile-once vs on-host, plus cache hits. AI did not invent prebuilt OpenResty; it made the boring multi-file project cheap enough to finish.
The hook: full deploys that mostly compile nginx
Bare-metal WSLProxy POPs historically ran an on-host OpenResty compile via Ansible (openresty.sh.j2). A DEPLOY_MODE=full or build job could spend a large fraction of wall clock on ./configure, make, luarocks, and opm — then fail for brittle host reasons (PATH, GCC, missing ldconfig, stale download URLs). Cold Next.js admin builds stacked more minutes on top. Everyone agrees “we should optimise the pipeline later.” Later rarely arrives.
Why developers normally do not get to it
Day-2 work is features, incidents, and customer-facing bugs. A painful but survivable full deploy stays on the backlog for months. The fix is cross-cutting: multi-stage Dockerfile, Hub credentials, GHA Buildx cache, Ansible extract/rsync that must not clobber live trees, DEPLOY_MODE defaults, npm/.next caches, and glibc/base-image coupling. Failure modes are opaque. Reviewers rightly fear wiping a POP’s nginx tree. Teams defer rather than risk it.
LHS — prebuilt image and Buildx/GHA cache
The left-hand side is an image factory. You are not “running OpenResty in Docker on the POP” as the primary story — you are using Docker as a reproducible build and distribution format, then extracting the tree onto bare metal.
- CI builds
infra/openresty-prebuilt/Dockerfilewith Buildx and pushesbwalia/wslproxy-openresty:<version>+:latest. build-openresty-prebuilt.ymlusescache-from/cache-to: type=gha,mode=maxso late-stage tweaks (for example the MinIO client URL) do not recompile everything from cold. Path filters mean routine Lua edits do not rebuild the image.- Ansible default
openresty_install_mode: prebuilt— controllerdocker pull→create→docker cp→ rsync to the target, excludingnginx/conf,nginx/html,nginx/logs. Seeopenresty_prebuilt.ymland the express helperopenresty-express-install.sh. - Platform today:
linux/amd64only. Base image defaults todebian:13— glibc/arch must match the host. Provenance and SBOM generation are disabled in that workflow by choice; document the trade-off rather than moralising.
One citeable warm rebuild after iterative fixes: Actions run 35500698371 (~3 minutes with cache after ldconfig/mc fixes). Other timings stay qualitative — do not invent percentage savings for cold compiles or dashboard rebuilds.
When should you rebuild the OpenResty image? When the compile inputs change: OpenResty version, luarocks/opm set, mc URL, or BASE_IMAGE. When should you not? Almost every day-2 Lua, rule, or server-JSON change.
RHS — delivery pipeline and DEPLOY_MODE
The right-hand side is the full deploy story. deploy-wslproxy-delivery-pipeline.yml promotes int → smoke → test → prod (lon1 / pop0 as configured). deploy-environment.yml maps mode to Ansible tags and, when the mode includes dashboard or is full, restores npm + Next.js .next/cache so admin rebuilds are not always cold.
- Routine day-2: prefer
code(Lua/api + data) — does not recompile OpenResty. build/full: OpenResty install path (prebuilt pull/extract, or legacy source if mode is overridden).dashboard/dashboard-next: UI rebuild with the cache steps above.nginx/servers/os_deps: scoped operations when you know the blast radius.
Readers should leave knowing three answers: when to rebuild the OpenResty image, when a code-only deploy is enough, and which GitHub files to copy as a pattern. Promotion discipline across environments pairs naturally with Ring Promoter — same “earn the next ring” mindset for edge releases. Product deep links stay on wslproxy.org.
Rules and much of the gateway behaviour reload from JSON without an nginx restart; config_status: true still drives compiled server conf and the reboot flag when listen/SSL blocks change. That separation is why day-2 can stay on code without pretending the edge is immutable.
How AI helped speed this process
Frame it as an operator + agent loop, not autopilot. Humans own intent, merge, Hub secrets, and POP risk. The agent accelerates mapping a slow job to role tags, scaffolding the Dockerfile/workflow/Ansible extract path, grepping legacy compile flags, reading failed Actions logs (ldconfig PATH, MinIO 410), and turning the landed tree into docs. The win is time-to-first-working-pipeline and stamina through two or three CI breakages — the exact reasons this work dies in normal sprints.
What stayed human: approving “image as artefact, not only runtime,” deciding which compile flags must stay identical to production, holding Hub credentials, merging to main/release, and choosing DEPLOY_MODE on a real POP. What the agent sped up: plan drafts, multi-file scaffolding, archaeology across Ansible templates, and the diagnose→patch→re-run loop when CI lied about being “flaky.”
AI did not invent prebuilt OpenResty — it made the boring, multi-file “make deploys stop compiling nginx every time” project cheap enough to finish.
Five portable tricks
| Trick | Where |
|---|---|
| Multi-stage: ship the tree, not the toolchain | infra/openresty-prebuilt/Dockerfile |
Stable layers first; volatile mc / app COPY last | Prebuilt + root Dockerfile |
Match BASE_IMAGE glibc/arch to the POP | Workflow dispatch inputs |
Pin versions (OPENRESTY_VERSION, lua-resty-jwt 0.2.3) | Dockerfile + role defaults |
| Cache Next/npm beside Docker layers | deploy-environment.yml |
Distinguish the Docker app image (Compose / Hub runtime) from the prebuilt OpenResty tree image (bare-metal packaging). They solve different problems. The root Dockerfile still teaches the classic lesson: install heavy binaries such as mc before COPY ./api so code churn does not invalidate the expensive layers. Optional rocks can warn; required JWT rock fails closed. System Lua 5.1 builds luarocks; LuaJIT runs the gateway tree — that split is documented in the Dockerfile comments for a reason.
Copy the same cache pattern elsewhere if you build CLI or control-plane images: build-wslproxy-cli.yml and the control-plane workflow both use Buildx type=gha caches beside the OpenResty prebuilt path.
War story: ldconfig PATH and MinIO 410
Debian slim images often omit /sbin from PATH. OpenResty configure then aborts: you need ldconfig when enabling LuaJIT. Fix: install libc-bin and set PATH to include /usr/sbin:/sbin. Separately, classic dl.min.io/client/mc/… URLs now return HTTP 410; the AIStor path is current in both Dockerfiles. Agent-readable logs accelerate the patch loop; humans still merge and push Hub credentials.
Those two failures are instructive because they look like infrastructure flakiness until you read the log once. A human-alone sprint often dies after the first mysterious red build. An operator+agent loop keeps enough context in the working set to try again the same afternoon — which is why this class of work finally landed instead of rotting as a nice-to-have.
What “done” looks like for operators
After a prebuilt install you should be able to run openresty -V (or the nginx binary under the OpenResty prefix) and hit the admin health endpoint for your environment. Day-2 deploys should default to code unless OpenResty or OS dependencies changed. If someone proposes full for a Lua typo fix, push back with the LHS/RHS diagram. Keep Hub credentials and BASE_IMAGE decisions in human hands; keep the scaffolding and log triage in the agent loop when it helps.
Where to go next
- Long technote — Dockerfile anatomy, Ansible extract, operator playbook, AI delivery loop.
- GitHub: bwalia/wslproxy · prebuilt Actions.
- Product: wslproxy.org · Workstation WSL Proxy · promotion patterns via ringpromoter.com.
Published by Workstation. Draft content — verify claims against origin/main before a live CMS publish.