Some checks failed
Check merge_group trigger on required workflows / Required workflows have merge_group trigger (pull_request) Successful in 7s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 7s
Retarget main PRs to staging / Retarget to staging (pull_request) Has been skipped
CI / Detect changes (pull_request) Successful in 9s
Lint curl status-code capture / Scan workflows for curl status-capture pollution (pull_request) Successful in 9s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 10s
E2E API Smoke Test / detect-changes (pull_request) Successful in 10s
Harness Replays / detect-changes (pull_request) Successful in 10s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 10s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 10s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 10s
CI / Python Lint & Test (pull_request) Successful in 6s
CI / Canvas (Next.js) (pull_request) Successful in 6s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 6s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 8s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 8s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 13s
Ops Scripts Tests / Ops scripts (unittest) (pull_request) Successful in 34s
Harness Replays / Harness Replays (pull_request) Failing after 33s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 53s
CodeQL / Analyze (${{ matrix.language }}) (go) (pull_request) Failing after 1m28s
CodeQL / Analyze (${{ matrix.language }}) (python) (pull_request) Failing after 1m29s
CodeQL / Analyze (${{ matrix.language }}) (javascript-typescript) (pull_request) Failing after 1m31s
CI / Platform (Go) (pull_request) Failing after 4m4s
publish-workspace-server-image.yml could not run on Gitea Actions because Dockerfile.tenant's stage 3 ran `git clone` against private Gitea repos from inside the Docker build context, where no auth path exists. Every workspace-server rebuild required a manual operator-host push. Move cloning to the trusted CI context (where AUTO_SYNC_TOKEN — the devops-engineer persona PAT — is naturally available). Dockerfile.tenant now COPYs from .tenant-bundle-deps/, populated by the workflow's new "Pre-clone manifest deps" step. The Gitea token never enters the image. - scripts/clone-manifest.sh: optional MOLECULE_GITEA_TOKEN env embeds basic-auth in the clone URL; redacted in log output. Anonymous fallback preserved for future public-repo path. - .github/workflows/publish-workspace-server-image.yml: new pre-clone step before docker build; injects AUTO_SYNC_TOKEN. Fail-fast if the secret is empty. - workspace-server/Dockerfile.tenant: drop stage 3 (templates), COPY from .tenant-bundle-deps/ instead. Header documents the prereq. - .gitignore: ignore /.tenant-bundle-deps/ so a local build can't accidentally commit cloned repos. Verified locally: clone-manifest.sh with the devops-engineer persona token cloned all 37 repos (9 ws + 7 org + 21 plugins, 4.9MB after .git strip). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
126 lines
5.9 KiB
Docker
126 lines
5.9 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 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
|
|
RUN CGO_ENABLED=0 GOOS=linux go build \
|
|
-ldflags "-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 \
|
|
-ldflags "-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 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
|
|
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
|
|
RUN chmod +x /entrypoint.sh && \
|
|
chown -R canvas:canvas /canvas /platform /memory-plugin /migrations
|
|
|
|
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"]
|