Skip to Content

Docker

Docker configuration for containerized Loop Health services.

Patient Graph API

The Patient Graph API uses a multi-stage Docker build optimized for production:

Dockerfile

Located at apps/patient-graph/Dockerfile:

FROM node:20-alpine AS base RUN corepack enable && corepack prepare pnpm@latest --activate FROM base AS deps WORKDIR /app COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./ # Copy package.json files for all needed packages COPY apps/patient-graph/package.json apps/patient-graph/ COPY packages/core/package.json packages/core/ COPY packages/shared/package.json packages/shared/ COPY packages/hono/package.json packages/hono/ COPY packages/patient-graph/package.json packages/patient-graph/ RUN pnpm install --frozen-lockfile --prod FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . RUN pnpm build --filter=@loop/patient-graph-api FROM base AS runner WORKDIR /app ENV NODE_ENV=production COPY --from=builder /app/apps/patient-graph/dist ./dist COPY --from=builder /app/node_modules ./node_modules EXPOSE 3000 CMD ["node", "dist/index.js"]

Build Stages

StagePurposeSize Impact
baseNode.js 20 Alpine + pnpm~120MB
depsInstall production dependenciesCached layer
builderBuild TypeScript to JavaScriptDiscarded
runnerFinal production image~200MB

Building Locally

# Build the image docker build -f apps/patient-graph/Dockerfile -t patient-graph-api . # Run locally docker run -p 3000:3000 \ -e DATABASE_URL="postgresql://..." \ -e CLERK_ISSUER_URL="https://..." \ patient-graph-api # Verify curl http://localhost:3000/health

Best Practices

Layer Caching

The Dockerfile is structured to maximize Docker layer caching:

  1. package.json files are copied first (rarely change)
  2. pnpm install is cached when dependencies don’t change
  3. Source code is copied last (changes frequently)

Security

  • Uses node:20-alpine for minimal attack surface
  • Runs as non-root user (Node.js default in Alpine)
  • Production dependencies only (--prod flag)
  • No source code in final image (only compiled JS)

Environment Variables

Never bake secrets into Docker images. Pass them at runtime:

docker run -p 3000:3000 \ --env-file .env.production \ patient-graph-api

Or use Fly.io secrets:

fly secrets set DATABASE_URL="postgresql://..."

Docker Compose (Development)

For local development with multiple services:

version: '3.8' services: patient-graph: build: context: . dockerfile: apps/patient-graph/Dockerfile ports: - "3000:3000" env_file: - .env.local depends_on: - db db: image: postgres:15-alpine environment: POSTGRES_DB: loop POSTGRES_USER: loop POSTGRES_PASSWORD: loop ports: - "5432:5432" volumes: - pgdata:/var/lib/postgresql/data volumes: pgdata: