// Docker orchestration and CI/CD for IntelliFill. Use when configuring containers, writing workflows, or setting up environments.
| name | docker-devops |
| description | Docker orchestration and CI/CD for IntelliFill. Use when configuring containers, writing workflows, or setting up environments. |
This skill provides comprehensive guidance for Docker orchestration, CI/CD pipelines, and deployment strategies in IntelliFill.
IntelliFill uses a multi-container architecture with Docker Compose.
IntelliFill/
โโโ quikadmin/ # Backend API container
โ โโโ Dockerfile.dev # Development build
โ โโโ Dockerfile.prod # Production build
โ โโโ Dockerfile.test # Testing build
โโโ quikadmin-web/ # Frontend UI container
โ โโโ Dockerfile.dev
โ โโโ Dockerfile.prod
โโโ docker-compose.yml # Development orchestration
โโโ docker-compose.e2e.yml # E2E testing orchestration
# Services in docker-compose.yml
services:
postgres: # PostgreSQL database
redis: # Redis cache & queues
backend: # Express API
frontend: # React UI
nginx: # Reverse proxy (production)
IntelliFill uses multi-stage builds for optimization.
# quikadmin/Dockerfile.dev
FROM node:18-alpine AS base
# Install dependencies for native modules
RUN apk add --no-cache \
python3 \
make \
g++ \
cairo-dev \
jpeg-dev \
pango-dev \
giflib-dev
WORKDIR /app
# Copy package files
COPY package*.json ./
COPY prisma ./prisma/
# Install dependencies
RUN npm ci
# Copy source code
COPY . .
# Generate Prisma Client
RUN npx prisma generate
# Expose port
EXPOSE 3002
# Development command with hot reload
CMD ["npm", "run", "dev"]
# quikadmin/Dockerfile.prod
FROM node:18-alpine AS builder
# Install build dependencies
RUN apk add --no-cache \
python3 \
make \
g++ \
cairo-dev \
jpeg-dev \
pango-dev
WORKDIR /app
# Copy package files
COPY package*.json ./
COPY prisma ./prisma/
# Install dependencies (production only)
RUN npm ci --only=production
# Copy source
COPY . .
# Generate Prisma Client
RUN npx prisma generate
# Build TypeScript
RUN npm run build
# Production stage
FROM node:18-alpine AS production
WORKDIR /app
# Copy from builder
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/prisma ./prisma
COPY --from=builder /app/package*.json ./
# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
USER nodejs
EXPOSE 3002
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node -e "require('http').get('http://localhost:3002/api/health', (r) => { process.exit(r.statusCode === 200 ? 0 : 1); });"
CMD ["node", "dist/index.js"]
# quikadmin-web/Dockerfile.dev
FROM oven/bun:1 AS base
WORKDIR /app
# Copy package files
COPY package.json bun.lock ./
# Install dependencies
RUN bun install
# Copy source
COPY . .
EXPOSE 8080
# Development server with hot reload
CMD ["bun", "run", "dev", "--host", "0.0.0.0"]
# quikadmin-web/Dockerfile.prod
FROM oven/bun:1 AS builder
WORKDIR /app
# Copy package files
COPY package.json bun.lock ./
# Install dependencies
RUN bun install
# Copy source
COPY . .
# Build production bundle
RUN bun run build
# Production stage with nginx
FROM nginx:alpine AS production
# Copy nginx config
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Copy built files
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
# Health check
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget --quiet --tries=1 --spider http://localhost/ || exit 1
CMD ["nginx", "-g", "daemon off;"]
# quikadmin/Dockerfile.test
FROM node:18-alpine
WORKDIR /app
# Install dependencies
RUN apk add --no-cache \
python3 \
make \
g++
# Copy package files
COPY package*.json ./
COPY prisma ./prisma/
# Install all dependencies (including devDependencies)
RUN npm ci
# Copy source
COPY . .
# Generate Prisma Client
RUN npx prisma generate
# Run tests
CMD ["npm", "run", "test:ci"]
IntelliFill uses Docker Compose for local development and E2E testing.
# docker-compose.yml
version: '3.8'
services:
postgres:
image: postgres:15-alpine
container_name: intellifill-postgres
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: intellifill
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: intellifill-redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: redis-server --appendonly yes
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
backend:
build:
context: ./quikadmin
dockerfile: Dockerfile.dev
container_name: intellifill-backend
environment:
NODE_ENV: development
PORT: 3002
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/intellifill
REDIS_URL: redis://redis:6379
ports:
- "3002:3002"
volumes:
- ./quikadmin:/app
- /app/node_modules
- backend_uploads:/app/uploads
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
command: sh -c "npx prisma migrate deploy && npm run dev"
frontend:
build:
context: ./quikadmin-web
dockerfile: Dockerfile.dev
container_name: intellifill-frontend
environment:
VITE_API_URL: http://localhost:3002/api
ports:
- "8080:8080"
volumes:
- ./quikadmin-web:/app
- /app/node_modules
depends_on:
- backend
volumes:
postgres_data:
redis_data:
backend_uploads:
# docker-compose.e2e.yml
version: '3.8'
services:
postgres-test:
image: postgres:15-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: intellifill_test
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
redis-test:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
backend-test:
build:
context: ./quikadmin
dockerfile: Dockerfile.dev
environment:
NODE_ENV: test
PORT: 3002
DATABASE_URL: postgresql://postgres:postgres@postgres-test:5432/intellifill_test
REDIS_URL: redis://redis-test:6379
ports:
- "3002:3002"
depends_on:
postgres-test:
condition: service_healthy
redis-test:
condition: service_healthy
command: sh -c "npx prisma migrate deploy && npm run dev"
frontend-test:
build:
context: ./quikadmin-web
dockerfile: Dockerfile.dev
environment:
VITE_API_URL: http://backend-test:3002/api
ports:
- "8080:8080"
depends_on:
- backend-test
e2e-runner:
build:
context: ./e2e
dockerfile: Dockerfile
environment:
BASE_URL: http://frontend-test:8080
API_URL: http://backend-test:3002/api
depends_on:
- frontend-test
- backend-test
command: npm run test:e2e
# docker-compose.prod.yml
version: '3.8'
services:
postgres:
image: postgres:15-alpine
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
restart: unless-stopped
backend:
build:
context: ./quikadmin
dockerfile: Dockerfile.prod
environment:
NODE_ENV: production
PORT: 3002
DATABASE_URL: ${DATABASE_URL}
REDIS_URL: redis://redis:6379
depends_on:
- postgres
- redis
restart: unless-stopped
frontend:
build:
context: ./quikadmin-web
dockerfile: Dockerfile.prod
depends_on:
- backend
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/ssl:/etc/nginx/ssl
depends_on:
- backend
- frontend
restart: unless-stopped
volumes:
postgres_data:
redis_data:
IntelliFill uses GitHub Actions for CI/CD.
# .github/workflows/test.yml
name: Tests
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
backend-tests:
name: Backend Tests
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15-alpine
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: intellifill_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
cache-dependency-path: quikadmin/package-lock.json
- name: Install dependencies
working-directory: quikadmin
run: npm ci
- name: Generate Prisma Client
working-directory: quikadmin
run: npx prisma generate
- name: Run migrations
working-directory: quikadmin
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/intellifill_test
run: npx prisma migrate deploy
- name: Run tests
working-directory: quikadmin
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/intellifill_test
REDIS_URL: redis://localhost:6379
run: npm run test:coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./quikadmin/coverage/lcov.info
flags: backend
frontend-tests:
name: Frontend Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v1
- name: Install dependencies
working-directory: quikadmin-web
run: bun install
- name: Run tests
working-directory: quikadmin-web
run: bun run test:coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./quikadmin-web/coverage/lcov.info
flags: frontend
e2e-tests:
name: E2E Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and run E2E tests
run: |
docker-compose -f docker-compose.e2e.yml up --abort-on-container-exit --exit-code-from e2e-runner
- name: Upload test results
if: always()
uses: actions/upload-artifact@v3
with:
name: e2e-results
path: e2e/test-results/
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
build:
name: Build and Push Docker Images
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push backend
uses: docker/build-push-action@v5
with:
context: ./quikadmin
file: ./quikadmin/Dockerfile.prod
push: true
tags: |
${{ secrets.DOCKER_USERNAME }}/intellifill-backend:latest
${{ secrets.DOCKER_USERNAME }}/intellifill-backend:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Build and push frontend
uses: docker/build-push-action@v5
with:
context: ./quikadmin-web
file: ./quikadmin-web/Dockerfile.prod
push: true
tags: |
${{ secrets.DOCKER_USERNAME }}/intellifill-frontend:latest
${{ secrets.DOCKER_USERNAME }}/intellifill-frontend:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
name: Deploy to Production
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /app/intellifill
docker-compose pull
docker-compose up -d
docker system prune -f
# .env.example (template)
NODE_ENV=development
PORT=3002
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/intellifill
DIRECT_URL=postgresql://user:password@localhost:5432/intellifill
# Redis
REDIS_URL=redis://localhost:6379
# Supabase
SUPABASE_URL=https://xxx.supabase.co
SUPABASE_ANON_KEY=xxx
SUPABASE_SERVICE_ROLE_KEY=xxx
# JWT
JWT_SECRET=your-secret-key
# docker-compose.yml
services:
backend:
env_file:
- ./quikadmin/.env
environment:
# Override specific variables
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/intellifill
# Dockerfile with build args
FROM node:18-alpine
ARG NODE_ENV=production
ENV NODE_ENV=$NODE_ENV
ARG API_URL
ENV VITE_API_URL=$API_URL
WORKDIR /app
# ...
# docker-compose.yml with build args
services:
frontend:
build:
context: ./quikadmin-web
args:
NODE_ENV: development
API_URL: http://localhost:3002/api
// quikadmin/src/api/health.routes.ts
import { Router } from 'express';
import prisma from '../utils/prisma';
import redis from '../utils/redis';
const router = Router();
router.get('/health', async (req, res) => {
try {
// Check database
await prisma.$queryRaw`SELECT 1`;
// Check Redis
await redis.ping();
res.status(200).json({
status: 'healthy',
timestamp: new Date().toISOString(),
services: {
database: 'up',
redis: 'up',
},
});
} catch (error) {
res.status(503).json({
status: 'unhealthy',
timestamp: new Date().toISOString(),
error: error.message,
});
}
});
export default router;
# Dockerfile with health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node -e "require('http').get('http://localhost:3002/api/health', (r) => { process.exit(r.statusCode === 200 ? 0 : 1); });"
# docker-compose.yml with health checks
services:
backend:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3002/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
#!/bin/bash
# deploy.sh
# Pull latest images
docker-compose pull
# Start new containers
docker-compose up -d --no-deps --scale backend=2 backend
# Wait for health check
sleep 10
# Stop old containers
docker-compose up -d --no-deps --scale backend=1 backend
# Clean up
docker system prune -f
# docker-compose.blue-green.yml
services:
backend-blue:
# Current production
backend-green:
# New version
nginx:
# Switch between blue/green
# Update one container at a time
for service in backend-1 backend-2 backend-3; do
docker-compose up -d $service
sleep 30 # Wait for health check
done