Files
Playa/Dockerfile
EugeneTes 28c6f90d91 feat(docker): bind the data volumes to /media/disk1/containers/volcanic_secretary_core
The entrypoint chowns the three mount roots before dropping to app, since
Docker creates missing bind directories as root.
2026-09-29 09:40:28 +00:00

91 lines
5.5 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; bind-mounted roots are
# chowned by the entrypoint (not recursively), since Docker creates missing host directories as root.
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.
# The shell only chowns the mount roots and execs setpriv, so the process tree is unchanged.
ENTRYPOINT ["/bin/sh", "-c", "chown 1654:1654 /data/workspaces /data/agent /data/claude; exec setpriv --reuid=1654 --regid=1654 --init-groups --inh-caps=-all --bounding-set=-all --no-new-privs -- dotnet Volcanic.AgenticCore.Telegram.dll"]