Entrypoint drops the capability bounding set and sets no_new_privs. Security docs and spec no longer claim the agent can't reach the secrets; docker exec fallbacks use a clean environment. PLAYWRIGHT_ENABLED drops the nested compose default. Tests pin the Dockerfile's Playwright install step and the compose model fallback.
89 lines
5.2 KiB
Docker
89 lines
5.2 KiB
Docker
# The Telegram host as one container. User guide: docs/docker.md.
|
|
# Spec: docs/superpowers/specs/2026-09-26-docker-deployment-design.md
|
|
|
|
FROM mcr.microsoft.com/dotnet/sdk:10.0-noble AS build
|
|
WORKDIR /src
|
|
|
|
# Restore layer: only the project files the host needs, cached until one of them changes.
|
|
COPY Directory.Build.props VolcanicAgenticCore.slnx ./
|
|
COPY src/Volcanic.AgenticCore.Abstractions/Volcanic.AgenticCore.Abstractions.csproj src/Volcanic.AgenticCore.Abstractions/
|
|
COPY src/Volcanic.AgenticCore/Volcanic.AgenticCore.csproj src/Volcanic.AgenticCore/
|
|
COPY extensions/Volcanic.AgenticCore.Extensions.ClaudeCode/Volcanic.AgenticCore.Extensions.ClaudeCode.csproj extensions/Volcanic.AgenticCore.Extensions.ClaudeCode/
|
|
COPY extensions/Volcanic.AgenticCore.Extensions.ClaudeCode.Bridge/Volcanic.AgenticCore.Extensions.ClaudeCode.Bridge.csproj extensions/Volcanic.AgenticCore.Extensions.ClaudeCode.Bridge/
|
|
COPY extensions/Volcanic.AgenticCore.Extensions.GoogleCalendar/Volcanic.AgenticCore.Extensions.GoogleCalendar.csproj extensions/Volcanic.AgenticCore.Extensions.GoogleCalendar/
|
|
COPY extensions/Volcanic.AgenticCore.Extensions.Mcp/Volcanic.AgenticCore.Extensions.Mcp.csproj extensions/Volcanic.AgenticCore.Extensions.Mcp/
|
|
COPY extensions/Volcanic.AgenticCore.Extensions.Scheduler/Volcanic.AgenticCore.Extensions.Scheduler.csproj extensions/Volcanic.AgenticCore.Extensions.Scheduler/
|
|
COPY hosts/Volcanic.AgenticCore.Telegram/Volcanic.AgenticCore.Telegram.csproj hosts/Volcanic.AgenticCore.Telegram/
|
|
RUN dotnet restore hosts/Volcanic.AgenticCore.Telegram/Volcanic.AgenticCore.Telegram.csproj
|
|
|
|
COPY src/ src/
|
|
COPY extensions/ extensions/
|
|
COPY hosts/ hosts/
|
|
# Framework-dependent: claude starts the bridge as `dotnet <Bridge>.dll`, so the loose dll and its runtimeconfig must exist.
|
|
RUN dotnet publish hosts/Volcanic.AgenticCore.Telegram/Volcanic.AgenticCore.Telegram.csproj -c Release --no-restore -o /out \
|
|
&& test -f /out/Volcanic.AgenticCore.Extensions.ClaudeCode.Bridge.dll \
|
|
&& test -f /out/Volcanic.AgenticCore.Extensions.ClaudeCode.Bridge.runtimeconfig.json
|
|
|
|
FROM mcr.microsoft.com/dotnet/runtime:10.0-noble
|
|
ARG DEBIAN_FRONTEND=noninteractive
|
|
ARG NODE_MAJOR=24
|
|
ARG CLAUDE_CODE_VERSION=2.1
|
|
ARG INSTALL_PLAYWRIGHT=false
|
|
# Must equal the version in PlaywrightPreset.DefaultPackage (the single source of truth);
|
|
# PlaywrightPresetTests.Dockerfile_version_matches_DefaultPackage fails when they differ.
|
|
ARG PLAYWRIGHT_MCP_VERSION=0.0.82
|
|
|
|
# What the agent's bash tool can run, then Node from NodeSource (Ubuntu noble's nodejs is too old).
|
|
RUN apt-get update \
|
|
&& apt-get install -y --no-install-recommends \
|
|
bash git ripgrep python3 python-is-python3 python3-venv curl ca-certificates tzdata jq less procps openssh-client gnupg \
|
|
&& mkdir -p /etc/apt/keyrings \
|
|
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
|
|
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODE_MAJOR}.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
|
|
&& apt-get update \
|
|
&& apt-get install -y --no-install-recommends nodejs \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# The global prefix stays root-owned, so claude's auto-updater can't replace it at runtime: an upgrade changes what
|
|
# system/init validation sees and invalidates the prompt cache, so it only arrives with a rebuild.
|
|
RUN npm install -g "@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}" \
|
|
&& claude --version \
|
|
&& npm cache clean --force
|
|
|
|
# Set even without Playwright, so an MCP server started with the host environment finds browsers here.
|
|
ENV PLAYWRIGHT_BROWSERS_PATH=/opt/ms-playwright
|
|
# Playwright MCP at the pinned version, and Chromium installed by that package's own playwright dependency, so browser
|
|
# build and library version match. Roughly +400 MB.
|
|
RUN if [ "$INSTALL_PLAYWRIGHT" = "true" ]; then \
|
|
npm install -g "@playwright/mcp@${PLAYWRIGHT_MCP_VERSION}" \
|
|
&& pw="$(npm root -g)/@playwright/mcp/node_modules/playwright/cli.js" \
|
|
&& test -f "$pw" \
|
|
&& node "$pw" install --with-deps chromium \
|
|
&& chmod -R a+rX "$PLAYWRIGHT_BROWSERS_PATH" \
|
|
&& playwright-mcp --version \
|
|
&& npm cache clean --force \
|
|
&& rm -rf /var/lib/apt/lists/*; \
|
|
fi
|
|
|
|
# The entrypoint hard-codes uid/gid 1654. Named volumes copy this ownership on first mount.
|
|
RUN test "$(id -u app)" = 1654 && test "$(id -g app)" = 1654 \
|
|
&& mkdir -p /data/workspaces /data/agent /data/claude /home/app \
|
|
&& chown -R app:app /data /home/app
|
|
|
|
COPY --from=build /out /app
|
|
WORKDIR /app
|
|
|
|
ENV Telegram__WorkspaceRoot=/data/workspaces \
|
|
VOLCANIC_AGENT_DIR=/data/agent \
|
|
CLAUDE_CONFIG_DIR=/data/claude \
|
|
HOME=/home/app \
|
|
DOTNET_CLI_TELEMETRY_OPTOUT=1 \
|
|
DOTNET_NOLOGO=1 \
|
|
TZ=UTC \
|
|
DOTNET_EnableDiagnostics=0
|
|
|
|
# No USER: PID 1 (docker-init with compose's init: true) must stay root, or the agent could read every secret from
|
|
# /proc/1/environ. setpriv drops to app and execs dotnet, so Environment.ProcessPath is the muxer the bridge needs.
|
|
# An empty bounding set and no_new_privs keep the agent's processes from regaining root through setuid binaries.
|
|
ENTRYPOINT ["setpriv", "--reuid=1654", "--regid=1654", "--init-groups", "--inh-caps=-all", "--bounding-set=-all", "--no-new-privs", "--", "dotnet", "Volcanic.AgenticCore.Telegram.dll"]
|