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-eon the command line — it leaks tops aux). - Restrict
.envfile 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