Skip to content

Docker Containerization

Connectum packages ship compiled JavaScript (.js + .d.ts + source maps), so they work on any Node.js version >= 22.13.0. If your own application code is written in TypeScript, you can either use Node.js 25+ (native type stripping for .ts files) or compile your code with a build tool before containerizing.

Full Example

A production Dockerfile is available in the car-sharing example.

Multi-Stage Dockerfile

Two-stage build: install dependencies in an isolated stage, then copy only production node_modules into a slim runtime image with a non-root user and health check.

See Dockerfile for the full listing.

Key highlights:

  • Stage 1 (deps) -- pnpm install --frozen-lockfile --prod for reproducible, minimal dependencies
  • Stage 2 (runtime) -- non-root node user, wget-based HEALTHCHECK against /healthz, native TypeScript via node src/index.ts
  • Environment defaults: NODE_ENV=production, PORT=5000, LOG_FORMAT=json, health and graceful shutdown enabled

Base image selection

If your own application code is compiled to JavaScript (e.g., via tsup or tsx), you can use any Node.js 22+ base image instead of node:25-slim. Use node:25-slim only when you want to run your own .ts files natively via Node.js type stripping.

Alternative Runtimes: Bun and tsx

You can replace the Node.js CMD with Bun or tsx:

dockerfile
# Node.js 25+ (native TypeScript for your own .ts files)
CMD ["node", "src/index.ts"]

# Bun
CMD ["bun", "src/index.ts"]

# tsx (works on Node.js 22+)
CMD ["npx", "tsx", "src/index.ts"]

When using tsx, you can use any Node.js 22+ base image (e.g., node:22-slim, node:24-slim) and add tsx as a dependency. Since @connectum/* packages ship compiled JavaScript, no special loader is needed for any runtime.

Alpine Variant (Smaller Image)

If you need a smaller image and do not depend on native modules requiring glibc, use the Alpine variant: swap both FROM node:25-slim lines in the Dockerfile for node:25-alpine. Verify the HEALTHCHECK command still resolves on Alpine (its BusyBox wget differs from the GNU build); adjust the probe command if needed.

Image Size Comparison

Base ImageApproximate SizeUse Case
node:25-slim~200 MBGeneral production (recommended)
node:25-alpine~140 MBSize-optimized, no native glibc modules
node:25~1 GBDevelopment only, avoid in production

.dockerignore

Keep images clean by excluding dependencies, tests, IDE files, dev configs, and proto sources. A minimal .dockerignore excludes node_modules, **/*.test.ts, .git, and editor/CI files so they never enter the build context.

Docker Compose for Local Development

For local development you typically compose your Connectum services with an observability stack. A representative docker-compose.yml wires up:

ServicePortDescription
order-service5000Connectum gRPC service
inventory-service5001Connectum gRPC service
otel-collector4317, 4318, 8889OpenTelemetry Collector (OTLP gRPC/HTTP, Prometheus)
jaeger16686Distributed tracing UI
prometheus9090Metrics collection
grafana3000Dashboards and visualization

Point each service's OTEL_EXPORTER_OTLP_ENDPOINT at the collector (http://otel-collector:4318) and let the collector fan traces out to Jaeger and metrics to Prometheus.

For a runnable observability stack wired to Connectum services, see the o11y-coroot example (it uses Coroot + ClickHouse + Prometheus rather than the Jaeger/Grafana stack tabulated above).

OTel Collector Configuration

A minimal collector config declares an OTLP receiver, a batch processor, and exporters for traces and metrics. The collector receives OTLP traces and metrics, batches them, and exports them to your tracing and metrics backends.

Image Optimization Tips

1. Layer Caching

Always copy package.json and pnpm-lock.yaml before source code. Docker caches the pnpm install layer and only re-runs it when dependencies change.

2. Production Dependencies Only

Use pnpm install --frozen-lockfile --prod to exclude devDependencies. This can reduce node_modules size by 50-70%.

3. Prune Unnecessary Files

After install, remove package manager caches:

dockerfile
RUN pnpm install --frozen-lockfile --prod \
    && pnpm store prune

4. Use .dockerignore Aggressively

Every file not needed at runtime should be in .dockerignore. This speeds up the build context transfer and reduces image size.

5. Pin Base Image Digests in Production

For reproducible builds, pin to a specific image digest:

dockerfile
FROM node:25-slim@sha256:<digest> AS runtime

WARNING

Never use the latest tag in production Dockerfiles. Always pin to a specific Node.js version (e.g., node:25.2.0-slim) to avoid unexpected breaking changes.

Environment Variables Reference

These environment variables configure a Connectum service inside a container:

VariableDescriptionDefault
NODE_ENVRuntime environmentdevelopment
PORTServer listen port5000
LISTENBind address0.0.0.0
LOG_LEVELLog verbosity (debug, info, warn, error)info
LOG_FORMATLog output format (json, pretty)json
LOG_BACKENDLogger backend (otel, pino, console)otel
HTTP_HEALTH_ENABLEDEnable HTTP health endpointsfalse
GRACEFUL_SHUTDOWN_ENABLEDEnable graceful shutdown on SIGTERM/SIGINTtrue
GRACEFUL_SHUTDOWN_TIMEOUT_MSShutdown timeout in ms30000
OTEL_SERVICE_NAMEOpenTelemetry service namenpm_package_name or unknown-service
OTEL_EXPORTER_OTLP_ENDPOINTOTLP collector endpoint--
OTEL_TRACES_EXPORTERTrace exporter (console, otlp/http, otlp/grpc, none)--
OTEL_METRICS_EXPORTERMetrics exporter--
OTEL_LOGS_EXPORTERLogs exporter--

What's Next