From 5a5b0ccb762ecceeaf7c1c63b4d7c79964b0ded4 Mon Sep 17 00:00:00 2001 From: HiranoMasaaki Date: Mon, 23 Feb 2026 07:52:24 +0000 Subject: [PATCH] feat: add official Docker Hub base image distribution Co-Authored-By: Claude Opus 4.6 --- .changeset/docker-hub-image.md | 6 +++ .dockerignore | 31 ++++++++++++ .github/workflows/docker.yml | 75 ++++++++++++++++++++++++++++ .gitignore | 2 + docker/Dockerfile | 66 ++++++++++++++++++++++++ docker/example/Dockerfile | 7 +++ docker/example/perstack.toml | 8 +++ docs/operating-experts/deployment.md | 25 ++++++---- package.json | 3 +- 9 files changed, 211 insertions(+), 12 deletions(-) create mode 100644 .changeset/docker-hub-image.md create mode 100644 .dockerignore create mode 100644 .github/workflows/docker.yml create mode 100644 docker/Dockerfile create mode 100644 docker/example/Dockerfile create mode 100644 docker/example/perstack.toml diff --git a/.changeset/docker-hub-image.md b/.changeset/docker-hub-image.md new file mode 100644 index 00000000..cbe50078 --- /dev/null +++ b/.changeset/docker-hub-image.md @@ -0,0 +1,6 @@ +--- +"perstack": patch +"@perstack/base": patch +--- + +feat: add official Docker Hub base image distribution diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..544eb61e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,31 @@ +**/node_modules +**/.turbo +**/dist +.git/ +.bun/ + +docs/ +e2e/ +benchmarks/ +coverage/ +clickhouse/ +docker/out/ + +.claude/ +.cursor/ +.idea/ +.vscode/ +.next/ + +.env +.env.local +.env.*.local +.npmrc +*.log +*.tsbuildinfo +*.swp +*.swo +*.tmp +*.temp +.DS_Store +Thumbs.db diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 00000000..58e16ecf --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,75 @@ +name: Docker + +on: + push: + tags: + - 'perstack@*' + workflow_dispatch: + inputs: + tag: + description: 'Tag to build (e.g. perstack@0.0.93). Uses HEAD if empty.' + required: false + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: read + +jobs: + docker: + name: Build & Push + runs-on: ubuntu-24.04 + steps: + - name: Extract version + id: version + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ -n "${{ inputs.tag }}" ]; then + TAG="${{ inputs.tag }}" + else + TAG="${GITHUB_REF#refs/tags/}" + fi + VERSION="${TAG#perstack@}" + MAJOR_MINOR="${VERSION%.*}" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "major_minor=$MAJOR_MINOR" >> "$GITHUB_OUTPUT" + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + # Only tag as latest on push events (actual releases), not manual dispatches + if [ "${{ github.event_name }}" = "push" ]; then + echo "is_release=true" >> "$GITHUB_OUTPUT" + else + echo "is_release=false" >> "$GITHUB_OUTPUT" + fi + + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ steps.version.outputs.tag }} + + - name: Setup QEMU + uses: docker/setup-qemu-action@v3 + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + file: docker/Dockerfile + platforms: linux/amd64,linux/arm64 + push: true + tags: | + perstack/perstack:${{ steps.version.outputs.version }} + perstack/perstack:${{ steps.version.outputs.major_minor }} + ${{ steps.version.outputs.is_release == 'true' && 'perstack/perstack:latest' || '' }} + perstack/perstack:sha-${{ github.sha }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.gitignore b/.gitignore index 80895e8e..e083eaf6 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ *.tsbuildinfo coverage/ dist/ +docker/out/ node_modules/ npm-debug.log* perstack.lock @@ -25,6 +26,7 @@ perstack.toml !examples/**/perstack.toml !benchmarks/**/perstack.toml !apps/create-expert/perstack.toml +!docker/example/perstack.toml perstack/ !apps/perstack Thumbs.db diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000..31748f24 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,66 @@ +# Build stage: compile standalone binary with Bun +FROM --platform=$BUILDPLATFORM oven/bun:1 AS build + +ARG TARGETARCH + +WORKDIR /app + +# Copy lockfile and workspace package.json files first for layer caching +COPY package.json bun.lock ./ + +# Apps +COPY apps/perstack/package.json apps/perstack/package.json +COPY apps/base/package.json apps/base/package.json +COPY apps/create-expert/package.json apps/create-expert/package.json +COPY apps/create-expert-skill/package.json apps/create-expert-skill/package.json + +# Packages +COPY packages/core/package.json packages/core/package.json +COPY packages/runtime/package.json packages/runtime/package.json +COPY packages/tui/package.json packages/tui/package.json +COPY packages/tui-components/package.json packages/tui-components/package.json +COPY packages/installer/package.json packages/installer/package.json +COPY packages/skill-manager/package.json packages/skill-manager/package.json +COPY packages/perstack-toml/package.json packages/perstack-toml/package.json +COPY packages/log/package.json packages/log/package.json +COPY packages/filesystem/package.json packages/filesystem/package.json +COPY packages/react/package.json packages/react/package.json + +# Providers +COPY packages/providers/core/package.json packages/providers/core/package.json +COPY packages/providers/anthropic/package.json packages/providers/anthropic/package.json +COPY packages/providers/openai/package.json packages/providers/openai/package.json +COPY packages/providers/google/package.json packages/providers/google/package.json +COPY packages/providers/azure-openai/package.json packages/providers/azure-openai/package.json +COPY packages/providers/bedrock/package.json packages/providers/bedrock/package.json +COPY packages/providers/vertex/package.json packages/providers/vertex/package.json +COPY packages/providers/ollama/package.json packages/providers/ollama/package.json +COPY packages/providers/deepseek/package.json packages/providers/deepseek/package.json + +RUN bun install --frozen-lockfile + +# Copy all source files +COPY . . + +# Stub optional peer dependency that ink imports but doesn't need at runtime +RUN mkdir -p node_modules/react-devtools-core && \ + echo "module.exports = { connectToDevTools() {} };" > node_modules/react-devtools-core/index.js + +# Compile standalone binary (~100MB: 98MB Bun runtime + 2MB app code) +RUN TARGET="bun-linux-$(echo $TARGETARCH | sed 's/amd64/x64/' | sed 's/arm64/aarch64/')" && \ + bun build apps/perstack/bin/cli.ts --compile --minify --target="$TARGET" --outfile=/app/out/perstack + +# Runtime stage: agentic AI runtime needs a usable shell environment +# Agents invoke exec to run arbitrary commands, so include common tools +FROM ubuntu:24.04 + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + ca-certificates curl jq && \ + rm -rf /var/lib/apt/lists/* + +COPY --from=build /app/out/perstack /usr/local/bin/perstack + +WORKDIR /workspace + +ENTRYPOINT ["perstack"] diff --git a/docker/example/Dockerfile b/docker/example/Dockerfile new file mode 100644 index 00000000..056cb4b0 --- /dev/null +++ b/docker/example/Dockerfile @@ -0,0 +1,7 @@ +FROM perstack/perstack:latest + +COPY perstack.toml . + +RUN perstack install + +ENTRYPOINT ["perstack", "run", "my-expert"] diff --git a/docker/example/perstack.toml b/docker/example/perstack.toml new file mode 100644 index 00000000..6cabe988 --- /dev/null +++ b/docker/example/perstack.toml @@ -0,0 +1,8 @@ +[experts.my-expert] +model = "claude-sonnet-4-5" +version = "1.0.0" +description = "A helpful assistant." +instruction = "You are a helpful assistant. Answer questions concisely." + +# @perstack/base runs in-memory by default (via InMemoryTransport). +# No additional configuration is needed for the base skill. diff --git a/docs/operating-experts/deployment.md b/docs/operating-experts/deployment.md index 93d3299b..6170e7b8 100644 --- a/docs/operating-experts/deployment.md +++ b/docs/operating-experts/deployment.md @@ -8,24 +8,27 @@ Perstack runs on any Node.js environment. Choose your deployment target based on ## Docker -Build a container image for your Expert: +The official [`perstack/perstack`](https://hub.docker.com/r/perstack/perstack) base image ships pre-compiled standalone binaries — no Node.js or Bun needed at runtime. ```dockerfile -FROM node:22-slim -WORKDIR /app -COPY perstack.toml /app/perstack.toml -RUN npm install -g perstack -ENTRYPOINT ["perstack", "run", "--config", "/app/perstack.toml"] +FROM perstack/perstack:latest +COPY perstack.toml . +RUN perstack install +ENTRYPOINT ["perstack", "run", "my-expert"] ``` -Run with isolation controls: - ```bash docker build -t my-expert . docker run --rm \ -e ANTHROPIC_API_KEY \ -v $(pwd)/workspace:/workspace \ - my-expert my-expert "query" + my-expert "query" +``` + +Pin to a specific version for reproducible builds: + +```dockerfile +FROM perstack/perstack:0.0.93 ``` For production, add resource limits and network restrictions. See [Isolation by Design](./isolation-by-design.md). @@ -121,8 +124,8 @@ Deploy Experts from the registry without building custom images: docker run --rm \ -e ANTHROPIC_API_KEY \ -v $(pwd)/workspace:/workspace \ - node:22-slim \ - npx perstack run @org/expert@1.0.0 "query" + perstack/perstack:latest \ + run @org/expert@1.0.0 "query" ``` Pin versions for reproducible deployments. diff --git a/package.json b/package.json index 080d33e4..90a7481e 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "validate:changeset": "bun scripts/validate-changeset.ts", "validate:versions": "bun scripts/validate-version-sync.ts", "check:schema-diff": "bun scripts/check-schema-diff.ts", - "validate:all": "bun run validate:versions && bun run validate:changeset && bun run check:schema-diff" + "validate:all": "bun run validate:versions && bun run validate:changeset && bun run check:schema-diff", + "docker:build": "docker buildx build -f docker/Dockerfile -t perstack/perstack:local ." }, "devDependencies": { "@biomejs/biome": "^2.4.2",