Repository navigation
feat(gateway): publish the ghcr.io/forestadmin/gateway Docker image - #1987
Conversation
The Gateway image merges both packages' runtime deps into one manifest, which refuses two ranges for the same package. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dist/healthcheck.js probes /health on the port the Gateway listens on: PORT, then its legacy aliases, then 3931 with mcp, 3450 with api only. Only a 200 passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Dockerfile modeled on agent-bff's: agent-bff's tracing preload with OTEL_SERVICE_NAME=forest-gateway, EXPOSE 3450 3931, HEALTHCHECK on dist/healthcheck.js - docker/: deps manifest and lockfile, closure check rooted at the Gateway, scan gate, smoke test (mcp, api, mcp,api, legacy port, degraded 503, clean stop) - docker-publish-gateway.yml, dispatched by build.yml on a new @forestadmin/gateway tag; sends gateway-published on the latest stable - the package is no longer private, so it reaches npm Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4 new issues
|
Test report
Not verified yet
Before merging
|
…the push - each pushed digest is an index once sbom is on; resolve it to its platform manifests before comparing with what :version points to - fetch tags and decide latest-stable in the step that pushes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Coverage Impact This PR will not change total coverage. Modified Files with Diff Coverage (2)
🛟 Help
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tonours
left a comment
There was a problem hiding this comment.
Spec (PRD-1339): conforms. The publish path, the arm64 build and the public pull stay unverified until the first release, as the description states.
| - name: Trigger gateway Docker image publish (if released) | ||
| if: ${{ !cancelled() }} | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GH_TOKEN }} |
There was a problem hiding this comment.
Claude Opus 5.5 (claude-opus-5-5): This diff meets the criteria for a security review, so please run /security-review locally before merging.
Triggers
.github/workflows/build.yml:321handsGH_TOKENto a new step that dispatches the image publish..github/workflows/docker-publish-gateway.yml:182grantspackages: writeto the build job that pushes..github/workflows/docker-publish-gateway.yml:320grantspackages: writeto the merge job that moves tags..github/workflows/docker-publish-gateway.yml:448sendsSERVER_DEPLOY_DISPATCH_TOKENto forestadmin-server.packages/gateway/docker/deps/yarn.lock:1adds the image production lockfile.packages/gateway/docker/build-deps-manifest.js:47pins security resolutions for the image dependencies.packages/mcp-server/package.json:26pinszodto4.6.5.
There was a problem hiding this comment.
/security-review run on this PR's own diff (against its base branch, compared with the BFF workflow and agent-bff/docker it is modeled on): no finding above the confidence bar.
Checked against each trigger:
build.ymldispatch withGH_TOKEN: same shape as the existing agent-bff step;VERSIONcomes from tags semantic-release just created and is passed quoted togh workflow run.packages: writeonbuildandmerge: both are gatedif: github.event_name != 'pull_request'; top-level permissions arecontents: read. The PR job (validate) has no registry login,push: false, nocache-to, no secret. The trigger ispull_request, notpull_request_target, so fork PRs get neither secrets nor a write token.versioninput (workflow_dispatch, write access only): reaches the shell throughenv:, checked against a strict semver regex before use; every derived tag (FULL/MINOR/MAJOR) flows from the validated output.buildchecks out the@forestadmin/gateway@<version>tag.SERVER_DEPLOY_DISPATCH_TOKEN: only inenv:, never echoed; the payload is built from the validated version; the job runs only on non-PR, latest-stable publishes.- Image: base pinned by digest, runs as
node, code stays root-owned (only/appitself is chowned), installs with--frozen-lockfile --ignore-scripts, no secret baked asARG/ENV. deps/yarn.lockandbuild-deps-manifest.jsresolutions: they add security pins (qs, lodash, hono, fast-uri, body-parser, propagator-jaeger), and nothing is loosened;scan-gate.jsis identical to agent-bff's, so Trivy still blocks fixable CRITICAL/HIGH.zodpin: aligns mcp-server with agent-bff on the version the root lockfile already resolved.
Also checked: the health probe only hits localhost on an env-resolved port; dropping private publishes only dist/**/*.js and .d.ts (same files as agent-bff), so neither docker/ nor any env file ships to npm.
Tonours
left a comment
There was a problem hiding this comment.
Approved after /validator run 1987-20261008121230-Tonours: nothing at or above should-fix posted, 0 suppressed.
76466fa
into
feature/prd-1338-unify-the-standalone-gateway-configuration-on-one-set-of-env

Summary
ghcr.io/forestadmin/gateway: the MCP and/or the API in one image, built and pushed on every@forestadmin/gatewayrelease. The MCP gets an image for the first time.agent-bff's image and workflow are untouched.The probe and the server resolve the port the same way:
Also:
mcp-serverpinszodto4.6.5likeagent-bff(the manifest builder refuses two ranges; the root lockfile already resolved 4.6.5).Decisions made during grilling:
OTEL_SERVICE_NAMEwins over aservice.nameinOTEL_RESOURCE_ATTRIBUTES.gateway-publishedgated likeagent-bff-published: latest stable only; fails whenSERVER_DEPLOY_DISPATCH_TOKENis missing. forestadmin-server ignores it until its receiver lands.Evidence
node build-deps-manifest.jsover the Gateway closure →Conflicting ranges for "zod".After:
yarn workspace @forestadmin/gateway test: 195/195, lint clean. New:healthcheck.test.ts(200 passes; 503, refused connection, invalid services/port,PORT=0fail with the reason),resolveListenPortdefaults and agreement withparseGatewayEnv.check-deps-closure.js:closure OK (7 packages);docker/deps/yarn.lockinstalls with--frozen-lockfilein the image.smoke-test.sh gateway:local(arm64, local build), all green:FOREST_GATEWAY_BASE_PATH=/ai), traces exported to a local Jaeger:HEALTHCHECKhealthy.fgw_key:POST /ai/api/agent/v1/internal_notes/list200; trace showsbff-api-keys/resolve200 ›forest-schema200 › agentGET /forest/internal_notes200 › activity logPOST+PATCH status200./.well-known/oauth-authorization-server/ai200 ›/ai/oauth/authorize302 ›/ai/oauth/token200 ›POST /ai/mcp401 then 200 once signed in; tool calls work.forest-gateway, noErrorlog line.Not verified yet:
build.yml, push,:latest,gateway-published): it checks out the@forestadmin/gateway@X.Y.Ztag, which only exists after the release. To check on the first release after merge.docker-publish-gateway.ymlnot linted with actionlint (YAML parsed).Merge Danger
Door: one-way
Merging releases
@forestadmin/gateway@1.0.0to npm (a published version cannot be reused) and pushes the first public image. Before merging:@forestadmin/gatewayon npm and set up trusted publishing; without it the npm publish fails and no image is built.Rollback: stop publishing the image. Nothing consumes it until the hosted API service switches to it.
Blast Radius: release-pipeline
New steps in
build.yml's release job (guarded by!cancelled(), exit 0 when there is no new tag).docker-publish-bff.ymland theagent-bffimage are untouched. Everyagent-bfformcp-serverrelease now also releases the Gateway (--deps.bump=override) and rebuilds its image.Stacked on #1980.
Fixes PRD-1339
🤖 Generated with Claude Code
Note
Add gateway Docker image build and publish pipeline to
ghcr.io/forestadmin/gatewaynodeuser with a Docker healthcheck@forestadmin/gatewaytags and dispatches the publish workflow with the highest new version@forestadmin/gatewayis no longer marked private in package.json;mcp-serverpinszodto exact 4.6.5 in package.json; latest gateway publication sends agateway-publishedrepository_dispatch event to ForestAdmin/forestadmin-serverMacroscope summarized c6d6c31.