# syntax=docker/dockerfile:1 # ── Build stage ─────────────────────────────────────────────────────────────── # Compile the static Noctra binary. CGO is disabled so the result runs on # any Linux without libc surprises — and so the compiler can cross-compile. # # Pin the build stage to BUILDPLATFORM (the native builder arch) and let Go # cross-compile to TARGETARCH. For a multi-arch build this keeps the Go # toolchain running natively instead of emulating the compiler under QEMU for # the non-native arch — much faster, with an identical static result. FROM --platform=$BUILDPLATFORM golang:1.23-bookworm AS build WORKDIR /src # Dependency layer first for caching. Noctra is stdlib-only today (no # go.sum); the wildcard keeps this working if dependencies are added later. COPY go.mod go.su[m] ./ RUN go mod download COPY . . ARG VERSION=docker # TARGETOS/TARGETARCH are injected by buildx for each target platform. They're # empty under a plain `docker build` (legacy builder / BuildKit off), so fall # back to the native arch via `go env` — keeping GOARCH= a literal assignment # prefix (a VAR=val produced by expansion is NOT treated as an assignment). ARG TARGETOS ARG TARGETARCH RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-$(go env GOARCH)} go build -trimpath \ -ldflags "-s -w -X main.version=${VERSION}" \ -o /out/noctra ./cmd/noctra # ── Runtime stage ───────────────────────────────────────────────────────────── # Noctra shells out to git (worktrees/clone), gh (PR creation), and a coding # agent CLI (claude/codex/copilot, all via npm), so the runtime needs all of # them. The node base supplies the agent runtime; git + gh on top. # Node 22+ is required: @github/copilot lists Node.js 22 as its prerequisite but # declares no `engines` field, so an older base installs cleanly yet fails at # runtime. We pin Node 24 (current LTS), comfortably above that floor; claude/codex # are happy on it too. FROM node:24-bookworm-slim AS runtime RUN apt-get update \ && apt-get install -y --no-install-recommends ca-certificates curl git gnupg \ && install -m 0755 -d /etc/apt/keyrings \ && curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \ -o /etc/apt/keyrings/githubcli-archive-keyring.gpg \ && chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \ && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \ > /etc/apt/sources.list.d/github-cli.list \ && apt-get update \ && apt-get install -y --no-install-recommends gh \ && rm -rf /var/lib/apt/lists/* # Bundle ALL coding-agent backends so AGENT_BACKEND=claude|codex|copilot works # out of the box. The GitHub Copilot CLI is the standalone agentic CLI # (@github/copilot, binary `copilot`) — NOT the `gh copilot` suggest/explain # extension, which can't edit files or take `--allow-all-tools -p`. Auth is # provided at runtime via ANTHROPIC_API_KEY / OPENAI_API_KEY / GH_TOKEN # (interactive subscription login isn't viable in a detached container). RUN npm install -g @anthropic-ai/claude-code @openai/codex @github/copilot \ && npm cache clean --force COPY --from=build /out/noctra /usr/local/bin/noctra COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh RUN chmod +x /usr/local/bin/docker-entrypoint.sh # All mutable state lives under /data so a single mounted volume persists the # repos cache, worktrees, logs, and the PR cursor across restarts. Each path is # an env override Noctra already honours. ENV REPOS_BASE=/data/repos \ WORKTREE_BASE=/data/worktrees \ LOG_DIR=/data/logs \ STATE_DB=/data/state.db \ STATE_FILE=/data/state.json \ GITHUB_AUTH_DIR=/data/github \ PLUGINS_DIR=/data/plugins WORKDIR /data VOLUME /data # The container's only process is Noctra (PID 1 via exec); if it dies the # container exits, so an orchestrator's restart policy is the health mechanism. ENTRYPOINT ["docker-entrypoint.sh"] CMD ["noctra"]