
Docker Best Practices
- 75 installs
- 8 repo stars
- Updated February 6, 2026
- hieutrtr/ai1-skills
Docker containerization patterns for Python/React projects: multi-stage builds, layer optimization, security hardening, and Docker Compose for local dev.
About
Covers multi-stage Docker builds for Python and React, layer and image-size optimization, non-root hardening, Trivy scanning, and a Compose stack for local development. A developer uses it when creating or optimizing Dockerfiles or setting up local containers.
- Multi-stage builds (python:3.12-slim, node:20-alpine to nginx:alpine)
- Non-root user and Trivy security scanning; Compose with Postgres and Redis
Docker Best Practices by the numbers
- 75 all-time installs (skills.sh)
- +1 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #608 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/hieutrtr/ai1-skills --skill docker-best-practicesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 75 |
|---|---|
| repo stars | ★ 8 |
| Last updated | February 6, 2026 |
| Repository | hieutrtr/ai1-skills ↗ |
What it does
Docker containerization patterns for Python/React projects: multi-stage builds, layer optimization, security hardening, and Docker Compose for local dev.
Files
Docker Best Practices
When to Use
Activate this skill when:
- Creating a new Dockerfile for a Python backend or React frontend
- Optimizing existing Docker images for smaller size or faster builds
- Setting up Docker Compose for local development
- Configuring multi-stage builds to separate build and runtime dependencies
- Hardening container security (non-root user, minimal base images)
- Running security scans on Docker images with Trivy
- Designing an image tagging strategy for CI/CD pipelines
- Troubleshooting Docker build failures or runtime issues
Do NOT use this skill for:
- Deployment orchestration or CI/CD pipelines (use
deployment-pipeline) - Kubernetes configuration or Helm charts
- Cloud infrastructure provisioning (Terraform, CloudFormation)
- Application code patterns (use
python-backend-expertorreact-frontend-expert)
Instructions
Multi-Stage Build Strategy
Multi-stage builds keep final images small by separating build-time and runtime dependencies.
Principle: Build in a full image, run in a minimal image. Only copy what is needed for runtime.
┌──────────────────────────────────┐
│ Stage 1: Builder │
│ Full SDK, build tools, deps │
│ Compile, install, build │
├──────────────────────────────────┤
│ Stage 2: Runtime │
│ Minimal base image │
│ COPY --from=builder artifacts │
│ Non-root user, health check │
└──────────────────────────────────┘Python Backend Dockerfile
See references/python-dockerfile-template for the complete template.
Key decisions for Python:
# Stage 1: Build dependencies
FROM python:3.12-slim AS builder
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential libpq-dev \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# Stage 2: Runtime
FROM python:3.12-slim AS runtime
# Install only runtime system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq5 curl \
&& rm -rf /var/lib/apt/lists/*
# Create non-root user
RUN groupadd -r appuser && useradd -r -g appuser -d /app -s /sbin/nologin appuser
WORKDIR /app
COPY --from=builder /install /usr/local
COPY src/ ./src/
COPY alembic/ ./alembic/
COPY alembic.ini .
RUN chown -R appuser:appuser /app
USER appuser
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]Why these choices:
python:3.12-sliminstead ofalpine-- avoids musl compatibility issues with binary wheels--no-cache-dir-- prevents pip cache from bloating the image--prefix=/install-- isolates installed packages for clean COPYlibpq5at runtime,libpq-devonly at build -- minimizes runtime dependenciescurlin runtime -- needed for HEALTHCHECK command
React Frontend Dockerfile
See references/react-dockerfile-template for the complete template.
Key decisions for React:
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --ignore-scripts
COPY . .
RUN npm run build
# Stage 2: Serve with Nginx
FROM nginx:alpine AS runtime
COPY --from=builder /app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup && \
chown -R appuser:appgroup /var/cache/nginx /var/log/nginx /etc/nginx/conf.d && \
touch /var/run/nginx.pid && chown appuser:appgroup /var/run/nginx.pid
USER appuser
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD wget -q --spider http://localhost:8080/ || exit 1
CMD ["nginx", "-g", "daemon off;"]Why these choices:
node:20-alpinefor build -- smallest Node image, only needed at build timenginx:alpinefor serving -- ~7MB base, production-grade static file servernpm ci-- deterministic installs from lockfile, faster thannpm install--ignore-scripts-- security measure, prevents running arbitrary scripts during install- Final image has NO Node.js runtime -- only static files + Nginx
Base Image Selection Guide
| Use Case | Base Image | Size | Notes |
|---|---|---|---|
| Python backend | python:3.12-slim | ~150MB | Best compatibility with binary wheels |
| Python backend (minimal) | python:3.12-alpine | ~50MB | May need musl workarounds for some packages |
| React build stage | node:20-alpine | ~130MB | Only used during build |
| React runtime | nginx:alpine | ~7MB | Production static file serving |
| Utility/scripts | alpine:3.19 | ~5MB | For helper containers |
Rules: 1. Never use latest tag -- always pin major.minor version 2. Prefer -slim variants for Python (avoids musl issues) 3. Prefer -alpine variants for Node.js and Nginx (smaller images) 4. Update base images monthly for security patches
Layer Optimization
Docker caches layers. Order instructions from least-changing to most-changing.
Optimal layer order:
# 1. Base image (changes rarely)
FROM python:3.12-slim
# 2. System dependencies (changes monthly)
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq5 curl && rm -rf /var/lib/apt/lists/*
# 3. Create user (changes never)
RUN groupadd -r appuser && useradd -r -g appuser appuser
# 4. Python dependencies (changes weekly)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 5. Application code (changes every commit)
COPY src/ ./src/
# 6. Runtime config (changes rarely)
USER appuser
EXPOSE 8000
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]Common mistakes to avoid:
# BAD: Copying everything before installing dependencies (busts cache)
COPY . .
RUN pip install -r requirements.txt
# GOOD: Copy only requirements first, then install, then copy code
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY src/ ./src/Minimize layers:
# BAD: Multiple RUN commands create multiple layers
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*
# GOOD: Single RUN command, single layer, clean up in same layer
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
&& rm -rf /var/lib/apt/lists/*.dockerignore
Always include a .dockerignore to prevent unnecessary files from entering the build context.
# Version control
.git
.gitignore
# Python
__pycache__
*.pyc
*.pyo
.pytest_cache
.mypy_cache
.ruff_cache
*.egg-info
dist/
build/
.venv/
venv/
# Node
node_modules/
npm-debug.log*
.next/
coverage/
# IDE
.vscode/
.idea/
*.swp
*.swo
# Docker
Dockerfile*
docker-compose*
.dockerignore
# Environment files
.env
.env.*
!.env.example
# Documentation
*.md
docs/
LICENSE
# CI/CD
.github/
.gitlab-ci.yml
# OS
.DS_Store
Thumbs.dbImpact of .dockerignore:
- Without it: Build context may be 500MB+ (node_modules, .git)
- With it: Build context typically 5-20MB
- Faster builds, no risk of leaking secrets from
.envfiles
Security Hardening
Non-Root User
Never run containers as root in production.
# Create a dedicated user with no shell and no home directory
RUN groupadd -r appuser && \
useradd -r -g appuser -d /app -s /sbin/nologin appuser
# Set ownership of application files
COPY --chown=appuser:appuser src/ ./src/
# Switch to non-root user
USER appuserSecurity Scanning with Trivy
Scan images for vulnerabilities before deployment.
# Install Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh
# Scan image for vulnerabilities
trivy image --severity HIGH,CRITICAL app-backend:latest
# Scan and fail if HIGH/CRITICAL vulnerabilities found
trivy image --exit-code 1 --severity HIGH,CRITICAL app-backend:latest
# Generate JSON report
trivy image --format json --output trivy-report.json app-backend:latest
# Scan Dockerfile for misconfigurations
trivy config DockerfileIntegrate into CI/CD:
- name: Trivy vulnerability scan
uses: aquasecurity/trivy-action@master
with:
image-ref: 'app-backend:${{ github.sha }}'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'Additional Security Measures
# Read-only filesystem where possible
# (set at runtime with docker run --read-only)
# No new privileges
# (set at runtime with docker run --security-opt=no-new-privileges)
# Drop all capabilities, add only what is needed
# (set at runtime with docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE)
# Use COPY instead of ADD (ADD can auto-extract tarballs, security risk)
COPY requirements.txt . # GOOD
# ADD requirements.txt . # AVOID unless you need tar extractionDocker Compose for Local Development
See references/docker-compose-template.yml for the full template.
Architecture:
┌────────────┐ ┌────────────┐
│ Frontend │ │ Backend │
│ React:3000 │───>│ FastAPI:8000│
└────────────┘ └─────┬──────┘
│
┌──────┴──────┐
│ │
┌────┴────┐ ┌────┴────┐
│PostgreSQL│ │ Redis │
│ :5432 │ │ :6379 │
└─────────┘ └─────────┘Key Compose features for development:
services:
backend:
build:
context: .
dockerfile: Dockerfile.backend
target: builder # Use builder stage for development (has dev tools)
volumes:
- ./src:/app/src # Hot reload
environment:
- DEBUG=true
- DATABASE_URL=postgresql+asyncpg://postgres:postgres@db:5432/app_dev
depends_on:
db:
condition: service_healthy
ports:
- "8000:8000"
frontend:
build:
context: ./frontend
target: builder
volumes:
- ./frontend/src:/app/src # Hot reload
ports:
- "3000:3000"
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
volumes:
pgdata:Essential Compose patterns:
- Use
depends_onwithcondition: service_healthyfor startup ordering - Mount source code as volumes for hot reloading in development
- Use named volumes for database persistence
- Set
target: builderto use the build stage with dev dependencies - Define health checks for all infrastructure services
Image Tagging Strategy
Use a consistent tagging strategy across all environments.
Tag format:
registry.example.com/app-backend:<tag>Tagging rules:
| Tag | When | Example | Purpose |
|---|---|---|---|
git-<sha> | Every build | git-a1b2c3d | Immutable reference to exact code |
branch-<name> | Every push | branch-main | Latest from branch (mutable) |
v<semver> | Release | v1.2.3 | Semantic version release |
latest | Production deploy | latest | Current production (mutable) |
staging | Staging deploy | staging | Current staging (mutable) |
Implementation:
# Build with git SHA tag (immutable)
GIT_SHA=$(git rev-parse --short HEAD)
docker build -t "app-backend:git-${GIT_SHA}" .
# Tag for the branch
BRANCH=$(git rev-parse --abbrev-ref HEAD)
docker tag "app-backend:git-${GIT_SHA}" "app-backend:branch-${BRANCH}"
# Tag for release
docker tag "app-backend:git-${GIT_SHA}" "app-backend:v1.2.3"
# Tag as latest for production
docker tag "app-backend:git-${GIT_SHA}" "app-backend:latest"Rules: 1. Always tag with git SHA -- this is the immutable, traceable reference 2. Never deploy using latest tag -- always use the SHA tag 3. Use latest only as a convenience alias after a successful production deploy 4. Include build metadata in image labels
# Add metadata labels
LABEL org.opencontainers.image.source="https://github.com/org/repo"
LABEL org.opencontainers.image.revision="${GIT_SHA}"
LABEL org.opencontainers.image.created="${BUILD_DATE}"Quick Reference
# Build images
docker build -t app-backend:$(git rev-parse --short HEAD) -f Dockerfile.backend .
docker build -t app-frontend:$(git rev-parse --short HEAD) -f Dockerfile.frontend .
# Start local development
docker compose up -d && docker compose logs -f backend
# Scan for vulnerabilities
trivy image --severity HIGH,CRITICAL app-backend:latest
# Check image sizes
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" | grep app-
# Clean up
docker image prune -f# =============================================================================
# Docker Compose -- Local Development Environment
# =============================================================================
# Services:
# - backend: FastAPI application with hot reload (port 8000)
# - frontend: React dev server with hot reload (port 3000)
# - db: PostgreSQL 16 with health check (port 5432)
# - redis: Redis 7 with health check (port 6379)
#
# Usage:
# docker compose up -d # Start all services
# docker compose logs -f backend # Follow backend logs
# docker compose down -v # Stop and remove volumes
# =============================================================================
services:
# ─── Backend (FastAPI) ───────────────────────────────────────────────────
backend:
build:
context: .
dockerfile: Dockerfile.backend
target: builder # Use builder stage for dev (includes build tools)
container_name: app-backend
ports:
- "8000:8000"
volumes:
# Mount source code for hot reloading
- ./src:/app/src:cached
- ./alembic:/app/alembic:cached
- ./alembic.ini:/app/alembic.ini:ro
# Do NOT mount node_modules or __pycache__
environment:
- APP_ENV=development
- DEBUG=true
- DATABASE_URL=postgresql+asyncpg://postgres:postgres@db:5432/app_dev
- REDIS_URL=redis://redis:6379/0
- SECRET_KEY=dev-secret-key-do-not-use-in-production
- JWT_SECRET_KEY=dev-jwt-secret-do-not-use-in-production
- LOG_LEVEL=DEBUG
- CORS_ORIGINS=http://localhost:3000
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
# Override CMD for development: enable auto-reload
command: >
uvicorn src.main:app
--host 0.0.0.0
--port 8000
--reload
--reload-dir src
--log-level debug
restart: unless-stopped
# ─── Frontend (React) ───────────────────────────────────────────────────
frontend:
build:
context: ./frontend
dockerfile: Dockerfile.frontend
target: deps # Use deps stage that has node_modules
container_name: app-frontend
ports:
- "3000:3000"
volumes:
# Mount source code for hot reloading
- ./frontend/src:/app/src:cached
- ./frontend/public:/app/public:cached
# Preserve node_modules from the image (do not override with host)
- /app/node_modules
environment:
- NODE_ENV=development
- REACT_APP_API_URL=http://localhost:8000
- REACT_APP_ENABLE_DEBUG_PANEL=true
- WATCHPACK_POLLING=true # Enable polling for file changes in Docker
depends_on:
backend:
condition: service_healthy
# Run React dev server
command: npm start
restart: unless-stopped
# ─── PostgreSQL ─────────────────────────────────────────────────────────
db:
image: postgres:16
container_name: app-db
ports:
- "5432:5432"
environment:
POSTGRES_DB: app_dev
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
# Performance tuning for development
POSTGRES_INITDB_ARGS: "--data-checksums"
volumes:
# Persist database data across restarts
- pgdata:/var/lib/postgresql/data
# Optional: initialization scripts
# - ./scripts/init-db.sql:/docker-entrypoint-initdb.d/01-init.sql:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d app_dev"]
interval: 5s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
# ─── Redis ──────────────────────────────────────────────────────────────
redis:
image: redis:7-alpine
container_name: app-redis
ports:
- "6379:6379"
volumes:
- redisdata:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
# Limit memory usage in development
command: redis-server --maxmemory 128mb --maxmemory-policy allkeys-lru
restart: unless-stopped
# ─── Named Volumes ──────────────────────────────────────────────────────────
volumes:
pgdata:
driver: local
redisdata:
driver: local
# ─── Networks ────────────────────────────────────────────────────────────────
# Default network is created automatically by Compose.
# All services can reach each other by service name (e.g., db, redis).
# =============================================================================
# Production Python Multi-Stage Dockerfile
# =============================================================================
# Base: python:3.12-slim (Debian-based, best binary wheel compatibility)
# Pattern: Builder stage installs dependencies, runtime stage copies only what
# is needed. Final image has no build tools, no pip cache, non-root user.
#
# Build:
# docker build -t app-backend:latest -f Dockerfile.backend .
#
# Run:
# docker run -p 8000:8000 --env-file .env app-backend:latest
# =============================================================================
# ---------------------------------------------------------------------------
# Stage 1: Builder -- install Python dependencies
# ---------------------------------------------------------------------------
FROM python:3.12-slim AS builder
WORKDIR /app
# Install build-time system dependencies
# - build-essential: gcc, make (needed for compiling C extensions)
# - libpq-dev: PostgreSQL client headers (needed for psycopg2)
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# Copy only requirements first for better layer caching
# This layer is rebuilt only when requirements.txt changes
COPY requirements.txt .
# Install Python dependencies into /install prefix
# --no-cache-dir: do not store pip cache (saves ~50MB)
# --prefix=/install: isolate packages for clean COPY to runtime stage
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# ---------------------------------------------------------------------------
# Stage 2: Runtime -- minimal production image
# ---------------------------------------------------------------------------
FROM python:3.12-slim AS runtime
# Metadata labels (OCI standard)
LABEL org.opencontainers.image.title="app-backend"
LABEL org.opencontainers.image.description="FastAPI backend application"
LABEL org.opencontainers.image.vendor="platform-team"
# Install runtime-only system dependencies
# - libpq5: PostgreSQL client library (runtime only, no headers)
# - curl: needed for HEALTHCHECK
RUN apt-get update && \
apt-get install -y --no-install-recommends \
libpq5 \
curl \
&& rm -rf /var/lib/apt/lists/*
# Create non-root user
# - No login shell (/sbin/nologin)
# - No home directory created
# - Dedicated group
RUN groupadd -r appuser && \
useradd -r -g appuser -d /app -s /sbin/nologin appuser
WORKDIR /app
# Copy installed Python packages from builder
COPY --from=builder /install /usr/local
# Copy application code
COPY --chown=appuser:appuser src/ ./src/
COPY --chown=appuser:appuser alembic/ ./alembic/
COPY --chown=appuser:appuser alembic.ini .
# Ensure the app directory is owned by appuser
RUN chown -R appuser:appuser /app
# Switch to non-root user
USER appuser
# Expose the application port
EXPOSE 8000
# Health check: verify the application is responding
# - interval: check every 30 seconds
# - timeout: fail if no response within 10 seconds
# - retries: mark unhealthy after 3 consecutive failures
# - start_period: give the app 15 seconds to start up
HEALTHCHECK --interval=30s --timeout=10s --retries=3 --start-period=15s \
CMD curl -f http://localhost:8000/health || exit 1
# Run with uvicorn
# - --host 0.0.0.0: bind to all interfaces (required in Docker)
# - --port 8000: match EXPOSE
# - --workers 4: production worker count (adjust based on CPU cores)
# - --access-log: enable access logging
CMD ["uvicorn", "src.main:app", \
"--host", "0.0.0.0", \
"--port", "8000", \
"--workers", "4", \
"--access-log"]
# =============================================================================
# Production React Multi-Stage Dockerfile
# =============================================================================
# Build stage: node:20-alpine (compile TypeScript, bundle React)
# Runtime stage: nginx:alpine (serve static files, ~7MB base)
#
# Final image contains NO Node.js runtime -- only static HTML/CSS/JS + Nginx.
#
# Build:
# docker build -t app-frontend:latest -f Dockerfile.frontend ./frontend
#
# Run:
# docker run -p 8080:8080 app-frontend:latest
# =============================================================================
# ---------------------------------------------------------------------------
# Stage 1: Dependencies -- install node_modules
# ---------------------------------------------------------------------------
FROM node:20-alpine AS deps
WORKDIR /app
# Copy only package files for better layer caching
# This layer is rebuilt only when package.json or lockfile changes
COPY package.json package-lock.json ./
# Install dependencies
# --ignore-scripts: security measure, do not run postinstall scripts
# npm ci: clean install from lockfile (deterministic, faster than npm install)
RUN npm ci --ignore-scripts
# ---------------------------------------------------------------------------
# Stage 2: Builder -- compile and bundle the application
# ---------------------------------------------------------------------------
FROM node:20-alpine AS builder
WORKDIR /app
# Copy node_modules from deps stage
COPY --from=deps /app/node_modules ./node_modules
# Copy source code
COPY . .
# Build the production bundle
# This creates the build/ directory with optimized static assets
RUN npm run build
# ---------------------------------------------------------------------------
# Stage 3: Runtime -- serve with Nginx
# ---------------------------------------------------------------------------
FROM nginx:alpine AS runtime
# Metadata labels
LABEL org.opencontainers.image.title="app-frontend"
LABEL org.opencontainers.image.description="React frontend served by Nginx"
LABEL org.opencontainers.image.vendor="platform-team"
# Create non-root user and group
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup
# Copy built static files from builder stage
COPY --from=builder /app/build /usr/share/nginx/html
# Copy custom Nginx configuration
# This handles SPA routing (all paths -> index.html)
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Adjust permissions for non-root Nginx operation
# Nginx needs to write to these directories at runtime
RUN chown -R appuser:appgroup /var/cache/nginx \
/var/log/nginx \
/etc/nginx/conf.d && \
touch /var/run/nginx.pid && \
chown appuser:appgroup /var/run/nginx.pid
# Switch to non-root user
USER appuser
# Use non-privileged port (8080 instead of 80)
EXPOSE 8080
# Health check using wget (available in alpine, unlike curl)
HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=10s \
CMD wget -q --spider http://localhost:8080/ || exit 1
# Run Nginx in foreground
CMD ["nginx", "-g", "daemon off;"]
# =============================================================================
# Required: nginx.conf
# =============================================================================
# Place this nginx.conf alongside the Dockerfile:
#
# server {
# listen 8080;
# server_name _;
# root /usr/share/nginx/html;
# index index.html;
#
# # SPA routing: serve index.html for all routes
# location / {
# try_files $uri $uri/ /index.html;
# }
#
# # Cache static assets aggressively
# location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
# expires 1y;
# add_header Cache-Control "public, immutable";
# }
#
# # Security headers
# add_header X-Frame-Options "SAMEORIGIN" always;
# add_header X-Content-Type-Options "nosniff" always;
# add_header X-XSS-Protection "1; mode=block" always;
# add_header Referrer-Policy "strict-origin-when-cross-origin" always;
#
# # Gzip compression
# gzip on;
# gzip_types text/plain text/css application/json application/javascript
# text/xml application/xml application/xml+rss text/javascript;
# }
# =============================================================================