# pds.js as a single container. # # Built from workspace source rather than from the published packages, so the # image always matches the commit it was built from and can be built before a # release reaches npm. FROM node:22-slim AS builder # corepack resolves the pnpm version from package.json's packageManager field, # and prompts before downloading it — which would hang a non-interactive build. ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 RUN corepack enable WORKDIR /app # better-sqlite3 publishes prebuilds for common platforms, but falls back to # compiling from source when there is no match for the target. RUN apt-get update \ && apt-get install -y --no-install-recommends python3 make g++ \ && rm -rf /var/lib/apt/lists/* # Manifests first, so dependency resolution caches independently of source # edits. pnpm checks the whole workspace against the lockfile, so every # member's manifest has to be present even though most go unused here. COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./ COPY docker/package.json docker/ COPY packages/blobs-deno/package.json packages/blobs-deno/ COPY packages/blobs-fs/package.json packages/blobs-fs/ COPY packages/blobs-s3/package.json packages/blobs-s3/ COPY packages/cloudflare/package.json packages/cloudflare/ COPY packages/core/package.json packages/core/ COPY packages/deno/package.json packages/deno/ COPY packages/lexicon-resolver/package.json packages/lexicon-resolver/ COPY packages/node/package.json packages/node/ COPY packages/readonly/package.json packages/readonly/ COPY packages/spaces/package.json packages/spaces/ COPY packages/storage-sqlite/package.json packages/storage-sqlite/ COPY examples/cloudflare/package.json examples/cloudflare/ COPY examples/deno/package.json examples/deno/ COPY examples/node/package.json examples/node/ COPY examples/runclub/package.json examples/runclub/ # better-sqlite3 needs its install script to run. The repo allows that in # .npmrc, but that file is also where npm auth tokens live, so it is passed # explicitly here rather than copied into a layer. RUN printf 'only-built-dependencies[]=better-sqlite3\n' > .npmrc \ && pnpm install --frozen-lockfile --prod --filter pds-container... # Source last: editing it invalidates only this layer. COPY packages packages COPY docker/server.js docker/ FROM node:22-slim AS runtime WORKDIR /app # Runs unprivileged. node:22-slim ships a "node" user for exactly this. RUN mkdir -p /data && chown node:node /data USER node COPY --from=builder --chown=node:node /app /app ENV NODE_ENV=production ENV PDS_DB_PATH=/data/pds.db ENV PDS_BLOBS_DIR=/data/blobs ENV PORT=3000 # PDS_HOSTNAME and JWT_SECRET are deliberately unset — the entrypoint exits # rather than inventing values for them. VOLUME /data EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD node -e "fetch('http://127.0.0.1:'+(process.env.PORT||3000)+'/xrpc/_health').then(r=>process.exit(r.ok?0:1),()=>process.exit(1))" CMD ["node", "docker/server.js"]