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 /app/node_modules ./node_modules
COPY /app/dist ./dist
COPY /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 /app/build /usr/share/nginx/html
# Copy config template for runtime injection
COPY /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 /app/dist ./dist
COPY /app/node_modules ./node_modules
COPY /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