# ---------- Builder ---------- FROM golang:1.26-bookworm AS builder WORKDIR /src # Cache modules first. COPY go.mod go.sum ./ RUN go mod download # Build the static (cgo-free, pure-Go sqlite) binary. COPY . . RUN CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o /out/jarvis ./cmd/jarvis # Install the sherlock credential broker + its MCP servers so the headless # Copilot agent gets live read-only access to Grafana, Gitea, and hosts. # Pinned to a released tag; the module is public so no auth is needed. # GOPROXY=direct + GOSUMDB=off because it is served from Charlie's Gitea, # not the public proxy/checksum database. ARG SHERLOCK_VERSION=v0.1.11 RUN GOBIN=/out GOFLAGS=-trimpath GOPROXY=direct GOSUMDB=off CGO_ENABLED=0 \ go install \ gitea.alexandru.macocian.me/amacocian/sherlock/cmd/sherlock@${SHERLOCK_VERSION} \ gitea.alexandru.macocian.me/amacocian/sherlock/cmd/grafana-mcp@${SHERLOCK_VERSION} \ gitea.alexandru.macocian.me/amacocian/sherlock/cmd/gitea-mcp@${SHERLOCK_VERSION} \ gitea.alexandru.macocian.me/amacocian/sherlock/cmd/gssh-mcp@${SHERLOCK_VERSION} # ---------- Runtime ---------- # node base image so the GitHub Copilot CLI (a Node package) runs headless. FROM node:22-bookworm-slim AS runtime # Runtime deps: git (clone Charlie repos for context), ca-certs, jq (parse # the Gitea API in the entrypoint), tini (PID 1 signal handling), gosu (drop # privileges to the jarvis user after fixing the bind-mount ownership), # gettext-base (envsubst, to render the sherlock config from env), libcap2-bin # (setcap: grant the jarvis binary CAP_SETUID/SETGID so it can drop the agent # to a separate unprivileged user without the whole service running as root). RUN apt-get update \ && apt-get install -y --no-install-recommends git ca-certificates jq tini curl gosu gettext-base libcap2-bin \ && rm -rf /var/lib/apt/lists/* # Install the GitHub Copilot CLI globally (provides the `copilot` binary). RUN npm install -g @github/copilot && npm cache clean --force # Two unprivileged users: # jarvis (10001) runs the service and owns /data (the SQLite DB). # copilot (10002) runs the --yolo agent + MCP subprocess tree, confined to # its own writable home; the mirrored repos are mounted read-only. # /context holds the cloned repos (locked read-only at startup by the # entrypoint); /data holds the SQLite DB. RUN useradd --create-home --uid 10001 jarvis \ && useradd --create-home --uid 10002 copilot \ && mkdir -p /context /data \ && chown -R jarvis:jarvis /context /data COPY --from=builder /out/jarvis /usr/local/bin/jarvis # Let the (non-root) jarvis service drop the agent subprocess to the copilot # user via setuid/setgid, while the service itself keeps running as jarvis. RUN setcap cap_setuid,cap_setgid+ep /usr/local/bin/jarvis # The mirrored repos are checked out root-owned and read-only; allow any user # (the copilot agent) to run read-only git commands inside them. RUN git config --system --add safe.directory '*' # sherlock CLI + MCP servers on PATH so `sherlock copilot` can spawn them. COPY --from=builder /out/sherlock /out/grafana-mcp /out/gitea-mcp /out/gssh-mcp /usr/local/bin/ COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh # The container starts as root so the entrypoint can chown the /data bind # mount (which Docker creates root-owned on the host), then drops to the # unprivileged jarvis user via gosu before running the service. WORKDIR /context ENV HOME="/home/jarvis" \ JARVIS_LISTEN=":8080" \ JARVIS_DB_PATH="/data/jarvis.db" \ JARVIS_WORKDIR="/context" \ JARVIS_AGENT_WORKDIR="/context/Charlie/project-charlie" \ JARVIS_ADD_DIRS="/context" \ JARVIS_COPILOT_USER="copilot" \ GITEA_URL="https://gitea.alexandru.macocian.me" \ GITEA_ORGS="Charlie" \ GRAFANA_URL="https://grafana.alexandru.macocian.me" \ GSSH_URL="https://terminal.alexandru.macocian.me" \ SHERLOCK_ISSUER="https://id.alexandru.macocian.me/application/o/sherlock-cli/" \ SHERLOCK_KEYRING="memory" \ SHERLOCK_CONFIG="/home/copilot/.config/sherlock/config.toml" EXPOSE 8080 VOLUME ["/data"] ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/entrypoint.sh"]