feat: add Docker support with GitHub Actions CI/CD

Add comprehensive Docker deployment with automated builds:

**Docker Configuration:**
- Multi-stage Dockerfile for optimized Next.js production builds
  - Stage 1: Install dependencies with pnpm
  - Stage 2: Build application with standalone output
  - Stage 3: Minimal runtime image with non-root user
  - Includes health check endpoint
  - Final image size optimized
- .dockerignore for efficient build context
- Enable standalone output in next.config.ts for Docker

**GitHub Actions Workflow:**
- Automated Docker image builds on push to main and tags
- Multi-platform support (linux/amd64, linux/arm64)
- Push to GitHub Container Registry (ghcr.io)
- Smart tagging strategy:
  - `latest` for main branch
  - `vX.X.X` for semver tags
  - `main-SHA` for commit-specific images
- Build cache optimization with GitHub Actions cache
- Artifact attestation for supply chain security

**Docker Compose:**
- Combined stack for UI + API
- Environment variable configuration
- Health checks for both services
- Automatic restart policies
- Shared network configuration

**Documentation:**
- Updated README with Docker deployment instructions
- Pre-built image usage from GHCR
- Docker Compose setup guide
- Local build instructions
- Available image tags reference

**Production Ready:**
- Images automatically published to ghcr.io/valknarness/pastel-ui
- Supports both x64 and ARM64 architectures
- Health checks for container orchestration
- Environment-based configuration
- Non-root user for security

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
valknarness
2025-11-07 14:36:30 +01:00
parent 93889ab9bd
commit 75e21646b3
6 changed files with 264 additions and 3 deletions

39
.dockerignore Normal file
View File

@@ -0,0 +1,39 @@
# Dependencies
node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Build outputs
.next
out
dist
build
# Testing
coverage
.nyc_output
# Environment
.env
.env*.local
# Git
.git
.gitignore
# IDE
.vscode
.idea
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Misc
*.log
.turbo

72
.github/workflows/docker.yml vendored Normal file
View File

@@ -0,0 +1,72 @@
name: Docker Build & Push
on:
push:
branches:
- main
tags:
- 'v*.*.*'
pull_request:
branches:
- main
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
NEXT_PUBLIC_API_URL=${{ vars.NEXT_PUBLIC_API_URL || 'http://localhost:3001' }}
- name: Generate artifact attestation
if: github.event_name != 'pull_request'
uses: actions/attest-build-provenance@v1
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.build-and-push.outputs.digest }}
push-to-registry: true

63
Dockerfile Normal file
View File

@@ -0,0 +1,63 @@
# Pastel UI - Production Docker Image
# Multi-stage build for optimized Next.js 16 application
# Stage 1: Dependencies
FROM node:20-alpine AS deps
RUN corepack enable && corepack prepare pnpm@latest --activate
WORKDIR /app
# Copy package files
COPY package.json pnpm-lock.yaml ./
# Install dependencies
RUN pnpm install --frozen-lockfile --prod=false
# Stage 2: Builder
FROM node:20-alpine AS builder
RUN corepack enable && corepack prepare pnpm@latest --activate
WORKDIR /app
# Copy dependencies from deps stage
COPY --from=deps /app/node_modules ./node_modules
# Copy source files
COPY . .
# Set build-time environment variables
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production
# Build the application
RUN pnpm build
# Stage 3: Runner
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
# Create non-root user
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs
# Copy built application
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/api/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
CMD ["node", "server.js"]

View File

@@ -356,14 +356,55 @@ vercel --prod
### Docker
#### Using Pre-built Image from GHCR
```bash
# Build
# Pull the latest image
docker pull ghcr.io/valknarness/pastel-ui:latest
# Run the container
docker run -p 3000:3000 \
-e NEXT_PUBLIC_API_URL=http://localhost:3001 \
ghcr.io/valknarness/pastel-ui:latest
```
#### Docker Compose (UI + API)
Run both Pastel UI and Pastel API together:
```bash
# Using docker-compose
docker-compose up -d
# View logs
docker-compose logs -f
# Stop
docker-compose down
```
#### Building Locally
```bash
# Build the image
docker build -t pastel-ui .
# Run
docker run -p 3000:3000 -e NEXT_PUBLIC_API_URL=https://api.pastel.com pastel-ui
# Run locally built image
docker run -p 3000:3000 \
-e NEXT_PUBLIC_API_URL=http://localhost:3001 \
pastel-ui
```
#### Available Docker Images
Images are automatically built and published to GitHub Container Registry:
- `ghcr.io/valknarness/pastel-ui:latest` - Latest main branch
- `ghcr.io/valknarness/pastel-ui:v1.0.0` - Specific version
- `ghcr.io/valknarness/pastel-ui:main-abc1234` - Commit SHA
Supported platforms: `linux/amd64`, `linux/arm64`
### Static Export
```bash

43
docker-compose.yml Normal file
View File

@@ -0,0 +1,43 @@
version: '3.8'
services:
pastel-ui:
build:
context: .
dockerfile: Dockerfile
image: ghcr.io/valknarness/pastel-ui:latest
container_name: pastel-ui
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL:-http://localhost:3001}
- NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
restart: unless-stopped
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
pastel-api:
image: ghcr.io/valknarness/pastel-api:latest
container_name: pastel-api
ports:
- "3001:3001"
environment:
- RUST_LOG=info
- PORT=3001
- HOST=0.0.0.0
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3001/api/v1/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
networks:
default:
name: pastel-network

View File

@@ -3,6 +3,9 @@ import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
reactStrictMode: true,
// Enable standalone output for Docker
output: 'standalone',
// React Compiler disabled for now (requires babel-plugin-react-compiler)
// experimental: {
// reactCompiler: true,