244263430d
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Waiting to run
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 4s
CI / Detect changes (pull_request) Successful in 9s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 20s
E2E API Smoke Test / detect-changes (pull_request) Successful in 14s
E2E Chat / detect-changes (pull_request) Successful in 12s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 22s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 6s
Harness Replays / detect-changes (pull_request) Successful in 14s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 8s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 11s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 10s
gate-check-v3 / gate-check (pull_request) Successful in 7s
qa-review / approved (pull_request) Failing after 5s
security-review / approved (pull_request) Failing after 5s
sop-checklist / na-declarations (pull_request) N/A: (none)
CI / Platform (Go) (pull_request) Successful in 2m49s
sop-checklist / all-items-acked (pull_request) Successful in 5s
sop-tier-check / tier-check (pull_request) Successful in 5s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m5s
CI / Canvas (Next.js) (pull_request) Successful in 6m40s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 7s
Harness Replays / Harness Replays (pull_request) Successful in 3s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 20s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 10s
E2E Chat / E2E Chat (pull_request) Failing after 1m22s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Failing after 1m43s
CI / Python Lint & Test (pull_request) Successful in 7m35s
CI / all-required (pull_request) Successful in 7m46s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
audit-force-merge / audit (pull_request) Successful in 6s
Mirror the pattern already used in molecule-controlplane/Dockerfile.
Currently workspace-server only sets -X buildinfo.GitSHA; add -trimpath
plus -s -w (strip symbol table + DWARF debug info) inside the same
-ldflags string. The -X GitSHA injection is preserved (verified via
strings(1) on locally-built binary).
Empirical local measurement (CGO_ENABLED=0 GOOS=linux GOARCH=amd64,
go 1.26.3, /platform binary only):
before 44,669,544 bytes (42 MB)
after 31,191,202 bytes (29 MB)
delta 13,478,342 bytes (12 MB) — 30.2% reduction
RFC#563 reports the published *image* deltas as 87 -> 61 MB (-26 MB,
~29%); the per-image figure is larger than the per-binary figure
because both /platform and /memory-plugin are stripped, and the
binary is one layer of the multi-layer image.
Flag semantics (Go 1.26):
-trimpath strip absolute build-host paths from object code
(also improves reproducibility)
-ldflags "-s -w" linker drops symbol table (-s) and DWARF debug
info (-w); -X-injected strings are NOT in the
symbol table so GitSHA survives stripping
Single-purpose change: only ws-server Dockerfile + Dockerfile.tenant
touched; no behavioral changes to the binaries themselves.
145 lines
7.2 KiB
Docker
145 lines
7.2 KiB
Docker
# Dockerfile.tenant — combined platform (Go) + canvas (Next.js) image.
|
|
#
|
|
# Serves both the API (Go on :8080) and the UI (Node.js on :3000) in a
|
|
# single container. Go reverse-proxies unknown routes to canvas.
|
|
#
|
|
# Templates + plugins are NOT cloned at build time. They are pre-cloned
|
|
# in the trusted CI context (or operator host) by
|
|
# `scripts/clone-manifest.sh` into `.tenant-bundle-deps/` and COPYed in.
|
|
# The reason: post-2026-05-06, every workspace-template-* repo on Gitea
|
|
# (codex, crewai, deepagents, gemini-cli, langgraph) plus all 7
|
|
# org-template-* repos are private, so the Docker build can't `git clone`
|
|
# from inside the build context — there's no auth path that doesn't leak
|
|
# the Gitea token into an image layer. Pre-cloning keeps the token in
|
|
# the CI environment only; the resulting image carries the cloned trees
|
|
# with `.git` already stripped (see clone-manifest.sh).
|
|
#
|
|
# Build context: repo root, with `.tenant-bundle-deps/` populated by:
|
|
#
|
|
# MOLECULE_GITEA_TOKEN=<persona-PAT> scripts/clone-manifest.sh \
|
|
# manifest.json \
|
|
# .tenant-bundle-deps/workspace-configs-templates \
|
|
# .tenant-bundle-deps/org-templates \
|
|
# .tenant-bundle-deps/plugins
|
|
#
|
|
# In CI this happens in publish-workspace-server-image.yml's "Pre-clone
|
|
# manifest deps" step (uses AUTO_SYNC_TOKEN = devops-engineer persona).
|
|
# For a manual operator-host build, source the same token from
|
|
# /etc/molecule-bootstrap/agent-secrets.env first.
|
|
#
|
|
# docker buildx build --platform linux/amd64 \
|
|
# -f workspace-server/Dockerfile.tenant \
|
|
# -t <ECR>/molecule-ai/platform-tenant:latest \
|
|
# --build-arg GIT_SHA=<sha> --build-arg NEXT_PUBLIC_PLATFORM_URL= \
|
|
# --push .
|
|
|
|
# ── Stage 1: Go platform binary ──────────────────────────────────────
|
|
FROM golang:1.25-alpine@sha256:c4ea15b4a7912716eb362a022e2b12317762eca387423760bc59c0f9ae69423c AS go-builder
|
|
WORKDIR /app
|
|
COPY workspace-server/go.mod workspace-server/go.sum ./
|
|
# github-app-auth plugin removed 2026-05-07 (#157): per-agent Gitea
|
|
# identities replaced GitHub-App tokens post-suspension. The sibling
|
|
# COPY + replace directive are gone.
|
|
RUN go mod download
|
|
COPY workspace-server/ .
|
|
|
|
# GIT_SHA is baked into the binary via -ldflags so /buildinfo can return
|
|
# it at runtime. CI passes ${{ github.sha }}; local builds default to
|
|
# "dev" so an unset value never reads as a real SHA.
|
|
#
|
|
# Why this matters: the redeploy verification step compares each tenant's
|
|
# /buildinfo against the SHA the workflow expects. If GIT_SHA isn't
|
|
# threaded through here, every tenant returns "dev" and the verification
|
|
# fails closed — which is the correct fail-direction (#2395 root fix).
|
|
ARG GIT_SHA=dev
|
|
# Build flags (RFC#563):
|
|
# -trimpath strip absolute build-host paths from the binary
|
|
# -ldflags "-s -w" omit symbol table (-s) and DWARF debug info (-w)
|
|
# -X ...GitSHA=... preserved — /buildinfo still returns the SHA at
|
|
# runtime. -s removes the symbol *table* but not
|
|
# -X-injected string vars (they live in static
|
|
# data, not in the symtab).
|
|
# Empirical local measurement: ~29% smaller (87→61MB) for /platform.
|
|
# Mirrors the pattern already in molecule-controlplane/Dockerfile.
|
|
RUN CGO_ENABLED=0 GOOS=linux go build \
|
|
-trimpath \
|
|
-ldflags "-s -w -X github.com/Molecule-AI/molecule-monorepo/platform/internal/buildinfo.GitSHA=${GIT_SHA}" \
|
|
-o /platform ./cmd/server
|
|
# Memory v2 sidecar binary (Memory v2 #2728). Bundled so an operator
|
|
# can activate cutover by flipping MEMORY_V2_CUTOVER=true without
|
|
# provisioning a separate service. See entrypoint-tenant.sh for the
|
|
# launch logic.
|
|
RUN CGO_ENABLED=0 GOOS=linux go build \
|
|
-trimpath \
|
|
-ldflags "-s -w -X github.com/Molecule-AI/molecule-monorepo/platform/internal/buildinfo.GitSHA=${GIT_SHA}" \
|
|
-o /memory-plugin ./cmd/memory-plugin-postgres
|
|
|
|
# ── Stage 2: Canvas Next.js standalone ────────────────────────────────
|
|
FROM node:20-alpine@sha256:afdf98210b07b586eb71fa22ba2e432e058e4cd1304d31ed60888755b8c865fb AS canvas-builder
|
|
WORKDIR /canvas
|
|
COPY canvas/package.json canvas/package-lock.json* ./
|
|
RUN npm install
|
|
COPY canvas/ .
|
|
ARG NEXT_PUBLIC_PLATFORM_URL=""
|
|
ARG NEXT_PUBLIC_WS_URL=""
|
|
ENV NEXT_PUBLIC_PLATFORM_URL=$NEXT_PUBLIC_PLATFORM_URL
|
|
ENV NEXT_PUBLIC_WS_URL=$NEXT_PUBLIC_WS_URL
|
|
RUN npm run build
|
|
|
|
# ── Stage 3: Runtime ──────────────────────────────────────────────────
|
|
FROM node:20-alpine@sha256:afdf98210b07b586eb71fa22ba2e432e058e4cd1304d31ed60888755b8c865fb
|
|
RUN apk add --no-cache ca-certificates git tzdata openssh-client aws-cli
|
|
|
|
# Non-root runtime for the Node.js canvas process.
|
|
# The Go binary (started by entrypoint.sh) is also non-root — the
|
|
# entrypoint runs as root only long enough to set volume ownership,
|
|
# then exec's as the 'canvas' user via su-exec / setpriv.
|
|
# The Go platform itself drops privileges after init.
|
|
#
|
|
# node:20-alpine ships with uid/gid 1000 already taken by `node`. Delete
|
|
# it first so we can recreate `canvas` at the same uid/gid without
|
|
# conflict. Previously plain addgroup/adduser at 1000 failed with
|
|
# "group 'node' in use" — blocked the tenant image build for hours
|
|
# 2026-04-21. Picking a different uid would break mounted volumes
|
|
# that expect 1000, so we keep the slot and rename the user.
|
|
RUN deluser --remove-home node 2>/dev/null || true; \
|
|
delgroup node 2>/dev/null || true; \
|
|
addgroup -g 1000 canvas && adduser -u 1000 -G canvas -s /bin/sh -D canvas
|
|
|
|
# Go platform binary + Memory v2 sidecar
|
|
COPY --from=go-builder /platform /platform
|
|
COPY --from=go-builder /memory-plugin /memory-plugin
|
|
COPY workspace-server/migrations /migrations
|
|
|
|
# Templates + plugins (pre-cloned by scripts/clone-manifest.sh in the
|
|
# trusted CI / operator-host context, .git already stripped — see
|
|
# .tenant-bundle-deps/ in the build context). The Gitea token used to
|
|
# clone them never enters this image.
|
|
COPY .tenant-bundle-deps/workspace-configs-templates /workspace-configs-templates
|
|
COPY .tenant-bundle-deps/org-templates /org-templates
|
|
COPY .tenant-bundle-deps/plugins /plugins
|
|
|
|
# Canvas standalone
|
|
WORKDIR /canvas
|
|
COPY --from=canvas-builder /canvas/.next/standalone ./
|
|
COPY --from=canvas-builder /canvas/.next/static ./.next/static
|
|
COPY --from=canvas-builder /canvas/public ./public
|
|
|
|
COPY workspace-server/entrypoint-tenant.sh /entrypoint.sh
|
|
# /org-templates must be writable by the canvas user — the !external
|
|
# resolver mkdirs <orgBaseDir>/.external-cache/<repo>/<sha>/ on first
|
|
# import to cache cross-repo subtree fetches (org_external.go,
|
|
# internal#77 / task #222). Without this chown the resolver fails with
|
|
# "mkdir cache root: permission denied" and POST /org/import returns
|
|
# 400 "org template expansion failed" for any template that uses
|
|
# !external (e.g. molecule-dev → dev-lead). Caught on staging-cplead-2
|
|
# 2026-05-10 — see internal incident debrief.
|
|
RUN chmod +x /entrypoint.sh && \
|
|
chown -R canvas:canvas /canvas /platform /memory-plugin /migrations /org-templates
|
|
|
|
EXPOSE 8080
|
|
# entrypoint.sh starts as root to fix volume perms, then drops to
|
|
# canvas user. The Go binary (PID 1 replacement) runs as non-root.
|
|
USER canvas
|
|
CMD ["/entrypoint.sh"]
|