mirror of
https://github.com/kbenestad/mdcms.git
synced 2026-06-18 15:24:32 +00:00
6.7 KiB
6.7 KiB
| title | sort | section-id | keywords | description | language |
|---|---|---|---|---|---|
| Docker | 120 | deployment | Docker, Dockerfile, docker-compose, container, containerisation, deployment | Containerising a Velox application with Docker and docker-compose | en |
Docker
Containerising your Velox application with Docker makes it portable across any infrastructure — from a simple VPS to a Kubernetes cluster. This guide covers building optimised Docker images and running multi-service stacks with docker-compose.
Dockerfile
A production-grade multi-stage Dockerfile:
# Stage 1: Install dependencies
FROM node:22-alpine AS deps
WORKDIR /app
# Copy package manifests only — enables Docker layer caching
COPY package.json package-lock.json ./
RUN npm ci --frozen-lockfile
# Stage 2: Build the application
FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Build args — pass at build time
ARG PUBLIC_BASE_URL
ARG NODE_ENV=production
ENV PUBLIC_BASE_URL=$PUBLIC_BASE_URL
ENV NODE_ENV=$NODE_ENV
RUN npm run build
# Stage 3: Production runtime
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3700
# Create non-root user for security
RUN addgroup --system --gid 1001 veloxgroup && \
adduser --system --uid 1001 veloxuser
# Copy only production artefacts
COPY --from=builder --chown=veloxuser:veloxgroup /app/.velox/output ./
COPY --from=builder --chown=veloxuser:veloxgroup /app/package.json ./
# Install production dependencies only
RUN npm ci --omit=dev --frozen-lockfile
USER veloxuser
EXPOSE 3700
CMD ["node", "server.js"]
Building and Running
# Build the image
docker build \
--build-arg PUBLIC_BASE_URL=https://example.com \
-t my-velox-app:latest .
# Run the container
docker run \
--env-file .env.production \
-p 3700:3700 \
--name velox-app \
my-velox-app:latest
# Run in background
docker run -d \
--env-file .env.production \
-p 3700:3700 \
--restart unless-stopped \
--name velox-app \
my-velox-app:latest
.dockerignore
Exclude files from the build context to speed up builds:
node_modules
.velox
.git
.gitignore
*.md
.env
.env.*
!.env.example
tests
*.test.*
*.spec.*
coverage
docker-compose
A complete stack with the app, PostgreSQL, and Redis:
# docker-compose.yml
version: '3.9'
services:
app:
build:
context: .
args:
PUBLIC_BASE_URL: ${PUBLIC_BASE_URL:-http://localhost:3700}
image: my-velox-app:latest
ports:
- "3700:3700"
environment:
NODE_ENV: production
DATABASE_URL: postgresql://velox:${DB_PASSWORD}@db:5432/velox
REDIS_URL: redis://redis:6379
SESSION_SECRET: ${SESSION_SECRET}
JWT_SECRET: ${JWT_SECRET}
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3700/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: velox
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: velox
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U velox"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
redis:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD} --appendonly yes
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "--no-auth-warning", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
- nginx_cache:/var/cache/nginx
depends_on:
- app
restart: unless-stopped
volumes:
postgres_data:
redis_data:
nginx_cache:
Nginx Reverse Proxy
# nginx/nginx.conf
events { worker_connections 1024; }
http {
upstream velox_app {
server app:3700;
keepalive 64;
}
# Redirect HTTP → HTTPS
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# Static assets with long cache
location /assets/ {
proxy_pass http://velox_app;
add_header Cache-Control "public, max-age=31536000, immutable";
}
# Application
location / {
proxy_pass http://velox_app;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
}
Health Check Endpoint
Add a health check to your application for Docker and load balancers:
// routes/api/health+server.ts
import { defineHandler, json } from 'velox/server';
import { db } from '$lib/db';
export const GET = defineHandler(async () => {
try {
await db.$queryRaw`SELECT 1`;
return json({ status: 'ok', timestamp: new Date().toISOString() });
} catch (error) {
return json({ status: 'error', error: String(error) }, { status: 503 });
}
});
Running Database Migrations
Run migrations as a separate one-shot container before starting the app:
# docker-compose.yml — add this service
migrate:
image: my-velox-app:latest
command: npx prisma migrate deploy
environment:
DATABASE_URL: postgresql://velox:${DB_PASSWORD}@db:5432/velox
depends_on:
db:
condition: service_healthy
restart: "no"
Container Best Practices
| Practice | Why |
|---|---|
| Multi-stage builds | Reduces final image size by 60–80% |
| Non-root user | Limits damage if the container is compromised |
| Read-only filesystem | Mount only what needs to be writable |
--restart unless-stopped |
Survives host reboots |
| Resource limits | Prevents a runaway container from affecting neighbours |
| Health checks | Enables zero-downtime rolling updates |
Set resource limits:
app:
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M