Skip to content

Commit 923861d

Browse files
authored
Merge pull request #3 from paperclipinc/feat/docker-ci
feat: production-grade Docker builds on push to main
2 parents 75afc92 + 685bcb0 commit 923861d

3 files changed

Lines changed: 155 additions & 10 deletions

File tree

.dockerignore

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,29 @@
11
.git
22
.github
3+
.claude
34
.paperclip
5+
.paperclip-local
46
.pnpm-store
7+
.vscode
8+
.idea
9+
510
node_modules
611
**/node_modules
712
coverage
813
data
914
tmp
15+
tmp-*
16+
dist
17+
18+
desktop
19+
docs
20+
evals
21+
tests
22+
docker
23+
1024
*.log
25+
*.md
26+
!packages/**/README.md
27+
.env
28+
.env.*
29+
.DS_Store

.github/workflows/docker.yml

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
name: Docker
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
workflow_dispatch:
8+
9+
concurrency:
10+
group: docker-${{ github.ref }}
11+
cancel-in-progress: true
12+
13+
env:
14+
REGISTRY: ghcr.io
15+
IMAGE_NAME: ${{ github.repository }}
16+
17+
jobs:
18+
build-and-push:
19+
runs-on: ubuntu-latest
20+
timeout-minutes: 30
21+
22+
permissions:
23+
contents: read
24+
packages: write
25+
attestations: write
26+
id-token: write
27+
28+
steps:
29+
- name: Checkout repository
30+
uses: actions/checkout@v4
31+
32+
- name: Set up Docker Buildx
33+
uses: docker/setup-buildx-action@v3
34+
35+
- name: Log in to GitHub Container Registry
36+
uses: docker/login-action@v3
37+
with:
38+
registry: ${{ env.REGISTRY }}
39+
username: ${{ github.actor }}
40+
password: ${{ secrets.GITHUB_TOKEN }}
41+
42+
- name: Extract metadata
43+
id: meta
44+
uses: docker/metadata-action@v5
45+
with:
46+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
47+
tags: |
48+
type=sha,prefix=
49+
type=raw,value=latest,enable={{is_default_branch}}
50+
type=raw,value={{date 'YYYY.MMDD'}},enable={{is_default_branch}}
51+
52+
- name: Build and push
53+
id: push
54+
uses: docker/build-push-action@v6
55+
with:
56+
context: .
57+
push: true
58+
tags: ${{ steps.meta.outputs.tags }}
59+
labels: ${{ steps.meta.outputs.labels }}
60+
platforms: linux/amd64,linux/arm64
61+
cache-from: type=gha
62+
cache-to: type=gha,mode=max
63+
provenance: true
64+
sbom: true
65+
66+
- name: Generate artifact attestation
67+
uses: actions/attest-build-provenance@v2
68+
with:
69+
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
70+
subject-digest: ${{ steps.push.outputs.digest }}
71+
push-to-registry: true

Dockerfile

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1-
FROM node:lts-trixie-slim AS base
1+
# ── Stage 1: base ─────────────────────────────────────────────
2+
# Pinned Node 22 on Debian Trixie slim for reproducibility.
3+
FROM node:22-trixie-slim AS base
4+
25
RUN apt-get update \
3-
&& apt-get install -y --no-install-recommends ca-certificates curl git \
6+
&& apt-get install -y --no-install-recommends \
7+
ca-certificates curl git tini \
48
&& rm -rf /var/lib/apt/lists/*
9+
510
RUN corepack enable
611

12+
# ── Stage 2: deps ─────────────────────────────────────────────
13+
# Install only production + build dependencies using lockfile.
714
FROM base AS deps
815
WORKDIR /app
16+
17+
# Copy workspace manifests first (layer caching)
918
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml .npmrc ./
1019
COPY cli/package.json cli/
1120
COPY server/package.json server/
@@ -20,23 +29,62 @@ COPY packages/adapters/gemini-local/package.json packages/adapters/gemini-local/
2029
COPY packages/adapters/openclaw-gateway/package.json packages/adapters/openclaw-gateway/
2130
COPY packages/adapters/opencode-local/package.json packages/adapters/opencode-local/
2231
COPY packages/adapters/pi-local/package.json packages/adapters/pi-local/
32+
COPY packages/plugins/sdk/package.json packages/plugins/sdk/
33+
COPY packages/plugins/create-paperclip-plugin/package.json packages/plugins/create-paperclip-plugin/
2334

2435
RUN pnpm install --frozen-lockfile
2536

37+
# ── Stage 3: build ────────────────────────────────────────────
38+
# Build UI and server from source.
2639
FROM base AS build
2740
WORKDIR /app
41+
2842
COPY --from=deps /app /app
2943
COPY . .
30-
RUN pnpm --filter @paperclipai/ui build
31-
RUN pnpm --filter @paperclipai/server build
32-
RUN test -f server/dist/index.js || (echo "ERROR: server build output missing" && exit 1)
3344

34-
FROM base AS production
45+
RUN pnpm --filter @paperclipai/ui build \
46+
&& pnpm --filter @paperclipai/server build \
47+
&& test -f server/dist/index.js || (echo "ERROR: server build output missing" && exit 1)
48+
49+
# Prune dev dependencies after build
50+
RUN pnpm prune --prod --no-optional
51+
52+
# ── Stage 4: production ───────────────────────────────────────
53+
# Minimal runtime image — no build tools, no source code bloat.
54+
FROM node:22-trixie-slim AS production
55+
56+
# Labels for container registries
57+
LABEL org.opencontainers.image.source="https://github.com/paperclipinc/paperclip"
58+
LABEL org.opencontainers.image.description="Paperclip — AI company orchestration platform"
59+
LABEL org.opencontainers.image.vendor="Paperclip Inc."
60+
LABEL org.opencontainers.image.licenses="MIT"
61+
62+
RUN apt-get update \
63+
&& apt-get install -y --no-install-recommends \
64+
ca-certificates curl git tini \
65+
&& rm -rf /var/lib/apt/lists/*
66+
67+
RUN corepack enable
68+
69+
# Install agent runtimes globally
70+
RUN npm install --global --omit=dev \
71+
@anthropic-ai/claude-code@latest \
72+
@openai/codex@latest \
73+
opencode-ai \
74+
&& npm cache clean --force
75+
76+
# Create data directory
77+
RUN mkdir -p /paperclip && chown node:node /paperclip
78+
3579
WORKDIR /app
36-
COPY --chown=node:node --from=build /app /app
37-
RUN npm install --global --omit=dev @anthropic-ai/claude-code@latest @openai/codex@latest opencode-ai \
38-
&& mkdir -p /paperclip \
39-
&& chown node:node /paperclip
80+
81+
# Copy only built artifacts and production dependencies
82+
COPY --chown=node:node --from=build /app/package.json /app/pnpm-workspace.yaml /app/.npmrc ./
83+
COPY --chown=node:node --from=build /app/node_modules ./node_modules
84+
COPY --chown=node:node --from=build /app/server ./server
85+
COPY --chown=node:node --from=build /app/ui/dist ./ui/dist
86+
COPY --chown=node:node --from=build /app/cli ./cli
87+
COPY --chown=node:node --from=build /app/packages ./packages
4088

4189
ENV NODE_ENV=production \
4290
HOME=/paperclip \
@@ -52,5 +100,12 @@ ENV NODE_ENV=production \
52100
VOLUME ["/paperclip"]
53101
EXPOSE 3100
54102

103+
# Run as non-root
55104
USER node
105+
106+
# Use tini as PID 1 for proper signal handling
107+
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
108+
CMD curl -fsS http://localhost:3100/api/health || exit 1
109+
110+
ENTRYPOINT ["tini", "--"]
56111
CMD ["node", "--import", "./server/node_modules/tsx/dist/loader.mjs", "server/dist/index.js"]

0 commit comments

Comments
 (0)