Docker Best Practices for Flux
Optimize your Docker images for size, security, and performance on the Flux network
Minimize Image Size
Smaller images deploy faster and use less bandwidth across the decentralized network
Security First
Run as non-root users and keep dependencies updated to reduce vulnerabilities
Optimize Caching
Layer your Dockerfile properly to maximize Docker build cache efficiency
Multi-Stage Builds
Separate build and runtime environments for minimal production images
1Multi-Stage Builds
Multi-stage builds allow you to separate build dependencies from runtime dependencies, dramatically reducing final image size.
Bad: Single Stage
Includes build tools in production image
Final size: ~500MB+
Good: Multi-Stage
Only includes runtime dependencies
Final size: ~50-100MB
# Stage 1: Build
FROM node:18-alpine AS builder
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install all dependencies (including devDependencies)
RUN npm ci
# Copy source code
COPY . .
# Build the application
RUN npm run build
# Stage 2: Production
FROM node:18-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install only production dependencies
RUN npm ci --only=production
# Copy built application from builder stage
COPY --from=builder /app/dist ./dist
# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
USER nodejs
EXPOSE 3000
CMD ["node", "dist/index.js"]Result: Build dependencies (TypeScript, webpack, etc.) stay in the builder stage. Production image only contains compiled code and runtime dependencies.
2Choose Minimal Base Images
Use the smallest base image that meets your needs. Alpine Linux variants are typically 5-10x smaller than standard images.
| Base Image | Size | Use Case | Recommended |
|---|---|---|---|
| node:18 | ~900MB | Full Debian system | ❌ |
| node:18-slim | ~170MB | Minimal Debian | ⚠️ |
| node:18-alpine | ~40MB | Alpine Linux | ✓ |
| python:3.11-alpine | ~50MB | Python on Alpine | ✓ |
| nginx:alpine | ~40MB | Static sites | ✓ |
Alpine Considerations:
Alpine uses musl libc instead of glibc. Some packages may need compilation. If you encounter issues, use -slim variants instead.
3Layer Optimization
Each instruction in a Dockerfile creates a new layer. Order matters for build cache efficiency.
❌ Poor Layer Ordering
FROM node:18-alpine
WORKDIR /app
# ❌ Copies everything first
COPY . .
# Cache breaks on ANY file change
RUN npm install
CMD ["node", "index.js"]Every code change invalidates npm install cache
✓ Optimized Layer Ordering
FROM node:18-alpine
WORKDIR /app
# ✓ Copy dependency files first
COPY package*.json ./
# This layer is cached unless deps change
RUN npm ci
# Copy source code last
COPY . .
CMD ["node", "index.js"]npm install only runs when dependencies change
Additional Layer Tips:
- Combine RUN commands with
&&to reduce layers - Use
.dockerignoreto exclude unnecessary files (node_modules, .git, etc.) - Clean up package manager caches in the same RUN command
# Install and clean up in one layer
RUN apk add --no-cache git curl && \
npm ci --only=production && \
npm cache clean --force && \
rm -rf /tmp/*4Security Best Practices
Never Run as Root
Running containers as root is a security risk. Always create and use a non-root user.
❌ Running as root
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm install
# Runs as root (dangerous!)
CMD ["node", "index.js"]✓ Non-root user
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm install
# Create and switch to non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001 && \
chown -R nodejs:nodejs /app
USER nodejs
CMD ["node", "index.js"]Other Security Practices
Pin specific image versions
Use node:18.17.0-alpine instead of node:18-alpine
Scan for vulnerabilities
Use docker scan yourimage:latest or Snyk to check for known vulnerabilities
Never include secrets in images
Use environment variables for secrets, never hardcode or COPY them into images
Minimize installed packages
Each package is a potential security vulnerability - only install what you need
5Implement Health Checks
Health checks help Flux monitor your application and restart it if it becomes unhealthy.
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# Health check endpoint
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node healthcheck.js
EXPOSE 3000
CMD ["node", "index.js"]const http = require('http');
const options = {
host: 'localhost',
port: 3000,
path: '/health',
timeout: 2000
};
const healthCheck = http.request(options, (res) => {
console.log(`Health check status: ${res.statusCode}`);
if (res.statusCode === 200) {
process.exit(0);
} else {
process.exit(1);
}
});
healthCheck.on('error', (err) => {
console.error('Health check failed:', err);
process.exit(1);
});
healthCheck.end();Health Check Parameters
--interval: Time between checks--timeout: Max time for check--start-period: Grace period on startup--retries: Failures before unhealthy
Alternative: Curl
HEALTHCHECK --interval=30s \
CMD wget --quiet --tries=1 \
--spider http://localhost:3000/health \
|| exit 16Environment Variables
Use environment variables for configuration that changes between environments (dev, staging, production).
In Your Application
// config.js
module.exports = {
port: process.env.PORT || 3000,
dbHost: process.env.DB_HOST || 'localhost',
dbPort: process.env.DB_PORT || 5432,
nodeEnv: process.env.NODE_ENV || 'development',
apiKey: process.env.API_KEY // required
};In Flux Deployment Spec
{
"name": "myapp",
"repotag": "myuser/myapp:latest",
"environmentParameters": [
"PORT=3000",
"DB_HOST=fluxpostgres_myapp",
"DB_PORT=5432",
"NODE_ENV=production",
"API_KEY=your_api_key_here"
]
}Security Warning:
Never include sensitive data in your Docker image. Always pass secrets via environment variables in the deployment spec.
7Common Mistakes to Avoid
❌ Installing development dependencies in production
Use npm ci --only=production to exclude devDependencies
# Good
RUN npm ci --only=production
# Also good (for multi-stage)
RUN npm ci --omit=dev❌ Not using .dockerignore
Exclude unnecessary files to reduce context size and build time
node_modules
npm-debug.log
.git
.gitignore
.env
.env.local
*.md
.vscode
.idea
dist
build
coverage
.DS_Store❌ Using latest tags
Pin specific versions for reproducible builds
# Bad
FROM node:latest
# Good
FROM node:18.17.0-alpinelatest can change unexpectedly and break your app
❌ Rebuilding dependencies on every code change
Copy package.json before source code to leverage layer caching (see Layer Optimization section)
❌ Exposing unnecessary ports
Only EXPOSE ports that your application actually listens on
# Only expose what you need
EXPOSE 3000
# Don't expose internal/debug ports unless necessaryComplete Example: Production-Ready Dockerfile
Putting it all together - a production-ready Dockerfile following all best practices:
# Use specific version for reproducibility
FROM node:18.17.0-alpine AS builder
# Set working directory
WORKDIR /app
# Copy dependency manifests
COPY package*.json ./
# Install all dependencies (including dev) for building
RUN npm ci
# Copy source code
COPY . .
# Build application
RUN npm run build
# Production stage
FROM node:18.17.0-alpine
WORKDIR /app
# Install security updates
RUN apk upgrade --no-cache
# Copy package files
COPY package*.json ./
# Install only production dependencies
RUN npm ci --only=production && \
npm cache clean --force
# Copy built application from builder
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/healthcheck.js ./
# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001 && \
chown -R nodejs:nodejs /app
# Switch to non-root user
USER nodejs
# Expose application port
EXPOSE 3000
# Add health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node healthcheck.js
# Start application
CMD ["node", "dist/index.js"]This Dockerfile includes:
- ✓ Multi-stage build for minimal image size
- ✓ Alpine base image
- ✓ Pinned version numbers
- ✓ Optimized layer ordering
- ✓ Non-root user
- ✓ Health check implementation
- ✓ Production-only dependencies
- ✓ Cache cleanup