← All Posts
DevOps

Deploying Paychainly with Docker: Multi-Stage Builds and Container Best Practices

May 21, 2026· 1 min read

Multi-Stage Dockerfile

FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --production=false
COPY . .
RUN npm run build

FROM node:22-alpine AS production
WORKDIR /app
COPY package*.json ./
RUN npm ci --production=true && npm cache clean --force
COPY --from=builder /app/dist ./dist
EXPOSE 3002
USER node
CMD ["node", "dist/main.js"]

docker-run.sh Pattern

Each service gets a dedicated run script instead of docker-compose to allow independent restarts:

#!/bin/bash
docker stop paychainly-api 2>/dev/null; docker rm paychainly-api 2>/dev/null

docker run -d   --name paychainly-api   --restart unless-stopped   --add-host=host.docker.internal:host-gateway   -p 3002:3002   --env-file /opt/paychainly/.env   --log-opt max-size=20m   --log-opt max-file=5   paychainly/api:latest

Networking

Containers connect to host PostgreSQL and Redis via host.docker.internal. Set in .env:

DB_HOST=host.docker.internal
REDIS_HOST=host.docker.internal

Secret Management

  • Pass secrets via --env-file (not -e on the command line — it leaks to ps aux).
  • Restrict .env file permissions: chmod 600 /opt/paychainly/.env.
  • Never bake secrets into the Docker image.

Log Rotation

The --log-opt max-size=20m --log-opt max-file=5 flags cap container logs at 100 MB total, preventing disk exhaustion on long-running nodes.

Health Check Integration

HEALTHCHECK --interval=30s --timeout=5s --retries=3   CMD wget -qO- http://localhost:3002/health || exit 1
← Back to Blog
DockerdeploymentNode.jsAlpineproduction