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

Dockerfile (Node.js Multi-Stage Example)dockerfile
# 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 ImageSizeUse CaseRecommended
node:18~900MBFull Debian system
node:18-slim~170MBMinimal Debian⚠️
node:18-alpine~40MBAlpine Linux
python:3.11-alpine~50MBPython on Alpine
nginx:alpine~40MBStatic 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 .dockerignore to exclude unnecessary files (node_modules, .git, etc.)
  • Clean up package manager caches in the same RUN command
Example: Combining Commandsdockerfile
# 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.

Dockerfile with Health Checkdockerfile
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"]
healthcheck.jsjavascript
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 1

6Environment 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

.dockerignoretext
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-alpine

latest 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 necessary

Complete Example: Production-Ready Dockerfile

Putting it all together - a production-ready Dockerfile following all best practices:

Dockerfile (Production Ready)dockerfile
# 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