Skip to main content

Dockerfile Guide

Learn how to create optimized Dockerfiles for your applications on the Strongly platform.

Basic Requirements

Every application must include a Dockerfile in the project root. The platform uses this to build your container image.

Minimal Dockerfile

FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

Dockerfile Best Practices

1. Use Official Base Images

Use official images from Docker Hub for reliability and security:

# Good: Official images
FROM node:18-alpine
FROM python:3.11-slim
FROM nginx:1.25-alpine

# Avoid: Unverified or outdated images
FROM random/node
FROM ubuntu:latest

2. Use Specific Tags

Always use specific version tags, never latest:

# Good: Specific versions
FROM node:18.17-alpine3.18
FROM python:3.11.5-slim-bookworm

# Bad: Latest tag
FROM node:latest
FROM python

3. Minimize Layer Count

Combine RUN commands to reduce image size:

# Good: Combined commands
RUN apt-get update && \
apt-get install -y --no-install-recommends \
curl \
git \
python3 && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

# Bad: Multiple RUN commands
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git
RUN apt-get install -y python3

4. Leverage Build Cache

Order commands from least to most frequently changing:

# Good: Dependencies first, code last
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .

# Bad: Code first invalidates cache
WORKDIR /app
COPY . .
RUN npm ci --only=production

5. Use .dockerignore

Create a .dockerignore file to exclude unnecessary files:

node_modules
npm-debug.log
.git
.env
.DS_Store
*.md
coverage
.vscode

Application-Specific Examples

Node.js Application

FROM node:18-alpine AS builder

WORKDIR /app

# Copy dependency files
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production

# Copy application code
COPY . .

# Build if needed (TypeScript, etc.)
RUN npm run build

# Production stage
FROM node:18-alpine

WORKDIR /app

# Copy from builder
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./

# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001

USER nodejs

EXPOSE 3000

CMD ["node", "dist/server.js"]

React Application

FROM node:18-alpine AS builder

WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm ci

# Copy source code
COPY . .

# Build arguments from manifest
ARG REACT_APP_API_URL
ARG REACT_APP_VERSION
ENV REACT_APP_API_URL=${REACT_APP_API_URL}
ENV REACT_APP_VERSION=${REACT_APP_VERSION}

# Build React app
RUN npm run build

# Production stage with nginx
FROM nginx:1.25-alpine

# Copy custom nginx config
COPY nginx.conf /etc/nginx/nginx.conf

# Copy built app
COPY --from=builder /app/build /usr/share/nginx/html

# Copy config template for runtime injection
COPY --from=builder /app/build/config.js /usr/share/nginx/html/config.js

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

nginx.conf for React:

events {
worker_connections 1024;
}

http {
include /etc/nginx/mime.types;
default_type application/octet-stream;

server {
listen 80;
root /usr/share/nginx/html;
index index.html;

# Enable gzip
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

# SPA routing - fallback to index.html
location / {
try_files $uri $uri/ /index.html;
}

# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}
}

Python Flask Application

FROM python:3.11-slim

WORKDIR /app

# Install system dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends \
gcc \
postgresql-client && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

# Copy requirements
COPY requirements.txt .

# Install Python dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY . .

# Create non-root user
RUN useradd -m -u 1001 appuser && \
chown -R appuser:appuser /app

USER appuser

EXPOSE 5000

# Use gunicorn for production
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "app:app"]

Static Site

FROM nginx:1.25-alpine

# Copy static files
COPY . /usr/share/nginx/html

# Copy custom nginx config if needed
COPY nginx.conf /etc/nginx/nginx.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

Multi-Stage Builds

Use multi-stage builds to minimize final image size:

# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Production stage
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
CMD ["node", "dist/server.js"]

Benefits:

  • Smaller final image (no build tools)
  • Faster deployment
  • More secure (fewer packages)

Using Build Arguments

Reference manifest build-time variables:

# Declare build arguments
ARG NODE_ENV
ARG REACT_APP_VERSION
ARG REACT_APP_API_URL

# Set as environment variables
ENV NODE_ENV=${NODE_ENV}
ENV REACT_APP_VERSION=${REACT_APP_VERSION}
ENV REACT_APP_API_URL=${REACT_APP_API_URL}

# Use in build commands
RUN echo "Building version ${REACT_APP_VERSION}"
RUN npm run build

Corresponding manifest:

env:
- name: NODE_ENV
value: "production"
buildtime: true
- name: REACT_APP_VERSION
value: "1.0.0"
buildtime: true
- name: REACT_APP_API_URL
value: "https://api.example.com"
buildtime: true

Security Best Practices

Run as Non-Root User

# Create non-root user
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup

# Change ownership
RUN chown -R appuser:appgroup /app

# Switch to non-root
USER appuser

Minimize Attack Surface

# Use minimal base images
FROM node:18-alpine # Instead of node:18

# Remove unnecessary packages
RUN apt-get remove -y build-essential && \
apt-get autoremove -y && \
apt-get clean

Scan for Vulnerabilities

The platform automatically scans images for vulnerabilities. Fix issues:

# Update packages
RUN apk update && apk upgrade

# Use latest base image
FROM node:18.17-alpine3.18 # Latest patch version

Performance Optimization

Optimize Layer Caching

# Copy package files first (changes less frequently)
COPY package*.json ./
RUN npm ci

# Copy code last (changes most frequently)
COPY . .

Use .dockerignore

Exclude unnecessary files to speed up builds:

node_modules
.git
.env
*.log
coverage
.vscode
.idea

Minimize Image Size

# Use alpine base images
FROM node:18-alpine # ~50MB vs node:18 (~900MB)

# Clean up after installing
RUN apt-get update && \
apt-get install -y package && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

# Remove dev dependencies
RUN npm ci --only=production

Health Check Implementation

Implement health check endpoint for Kubernetes probes:

Express.js

app.get('/health', (req, res) => {
res.status(200).json({
status: 'ok',
timestamp: new Date().toISOString()
});
});

Flask

@app.route('/health')
def health():
return {
'status': 'ok',
'timestamp': datetime.now().isoformat()
}, 200

Nginx (Static Sites)

Add to nginx.conf:

location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}

Common Issues

Build Timeout

Problem: Build exceeds 10-minute limit

Solutions:

  • Use multi-stage builds
  • Leverage layer caching
  • Minimize dependencies
  • Use .dockerignore

Large Image Size

Problem: Image is too large, slow to deploy

Solutions:

  • Use alpine base images
  • Remove build dependencies in same RUN command
  • Use multi-stage builds
  • Clean package manager cache

Permission Errors

Problem: App can't write files

Solutions:

  • Run as non-root user
  • Set correct file permissions
  • Use proper USER directive
RUN chown -R appuser:appgroup /app
USER appuser

Missing Dependencies

Problem: App crashes due to missing system packages

Solutions:

  • Install required system packages
  • Test locally with Docker
  • Check application logs
RUN apt-get update && \
apt-get install -y --no-install-recommends \
libpq-dev \
gcc && \
apt-get clean

Testing Locally

Test your Dockerfile before deploying:

# Build image
docker build -t my-app:test .

# Run container
docker run -p 3000:3000 my-app:test

# Check health endpoint
curl http://localhost:3000/health

# Inspect image size
docker images my-app:test

# Check for vulnerabilities (optional)
docker scan my-app:test

Next Steps