diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..3cc1428c4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,99 @@ +# Dependencies +node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Testing +coverage +.nyc_output +*.test.ts +*.test.tsx +*.spec.ts +*.spec.tsx +__tests__ +__mocks__ +.vitest + +# Next.js +.next +out +dist +build + +# Production +/build + +# Misc +.DS_Store +*.pem + +# Debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Local env files +.env +.env*.local +.env.development +.env.test +.env.production + +# Vercel +.vercel + +# TypeScript +*.tsbuildinfo +next-env.d.ts + +# IDE +.vscode +.idea +*.swp +*.swo +*~ + +# Git +.git +.gitignore +.gitattributes + +# Documentation +*.md +docs +README* +CHANGELOG* +LICENSE + +# CI/CD +.github +.gitlab-ci.yml +azure-pipelines.yml + +# Docker +Dockerfile* +docker-compose* +.dockerignore + +# Development +.editorconfig +.prettierrc* +.eslintrc* +.eslintignore + +# Storybook +.storybook +storybook-static + +# E2E +e2e +playwright-report +test-results + +# Temporary files +tmp +temp +.tmp +.cache diff --git a/.github/workflows/container-build.yml b/.github/workflows/container-build.yml new file mode 100644 index 000000000..12cbe283a --- /dev/null +++ b/.github/workflows/container-build.yml @@ -0,0 +1,138 @@ +name: Build and Push GHCR Images + +on: + push: + branches: + - main + - develop + tags: + - 'v*.*.*' + pull_request: + branches: + - main + - develop + workflow_dispatch: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +permissions: + contents: read + packages: write + id-token: write + +jobs: + build-and-push: + name: Build and Push Docker Images + runs-on: ubuntu-latest + strategy: + matrix: + include: + - image: nextjs-app + context: . + dockerfile: ./frontends/nextjs/Dockerfile + platforms: linux/amd64,linux/arm64 + - image: dbal-daemon + context: ./dbal/production + dockerfile: ./dbal/production/build-config/Dockerfile + platforms: linux/amd64,linux/arm64 + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + 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 }}/${{ matrix.image }} + 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@v6 + with: + context: ${{ matrix.context }} + file: ${{ matrix.dockerfile }} + platforms: ${{ matrix.platforms }} + 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: | + BUILD_DATE=${{ github.event.head_commit.timestamp }} + VCS_REF=${{ github.sha }} + VERSION=${{ steps.meta.outputs.version }} + + - name: Generate artifact attestation + if: github.event_name != 'pull_request' + uses: actions/attest-build-provenance@v2 + with: + subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ matrix.image }} + subject-digest: ${{ steps.build.outputs.digest }} + push-to-registry: true + + security-scan: + name: Security Scan Images + runs-on: ubuntu-latest + needs: build-and-push + if: github.event_name != 'pull_request' + strategy: + matrix: + image: [nextjs-app, dbal-daemon] + steps: + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ matrix.image }}:${{ github.ref_name }} + format: 'sarif' + output: 'trivy-results-${{ matrix.image }}.sarif' + + - name: Upload Trivy results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: 'trivy-results-${{ matrix.image }}.sarif' + category: container-${{ matrix.image }} + + publish-manifest: + name: Create Multi-Arch Manifest + runs-on: ubuntu-latest + needs: build-and-push + if: github.event_name != 'pull_request' + steps: + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Create and push manifest for all images + run: | + for image in nextjs-app dbal-daemon; do + docker manifest create \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/$image:${{ github.ref_name }} \ + --amend ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/$image:${{ github.ref_name }}-amd64 \ + --amend ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/$image:${{ github.ref_name }}-arm64 + docker manifest push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/$image:${{ github.ref_name }} + done diff --git a/docker-compose.ghcr.yml b/docker-compose.ghcr.yml new file mode 100644 index 000000000..52dbd5698 --- /dev/null +++ b/docker-compose.ghcr.yml @@ -0,0 +1,110 @@ +version: '3.9' + +services: + # MetaBuilder Next.js App + nextjs-app: + image: ghcr.io/${GITHUB_REPOSITORY:-johndoe6345789/metabuilder}/nextjs-app:${IMAGE_TAG:-latest} + container_name: metabuilder-nextjs + ports: + - "3000:3000" + environment: + - NODE_ENV=production + - DATABASE_URL=file:/app/data/metabuilder.db + - DBAL_API_URL=http://dbal-daemon:8080 + - DBAL_WS_URL=ws://dbal-daemon:50051 + - NEXTAUTH_URL=http://localhost:3000 + - NEXTAUTH_SECRET=${NEXTAUTH_SECRET:-change-me-in-production} + volumes: + - metabuilder-data:/app/data + depends_on: + - dbal-daemon + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 40s + networks: + - metabuilder-network + + # DBAL Daemon (Production) + dbal-daemon: + image: ghcr.io/${GITHUB_REPOSITORY:-johndoe6345789/metabuilder}/dbal-daemon:${IMAGE_TAG:-latest} + container_name: metabuilder-dbal + ports: + - "8080:8080" + - "50051:50051" + environment: + - NODE_ENV=production + - DATABASE_URL=file:/app/data/metabuilder.db + - LOG_LEVEL=info + - ENABLE_METRICS=true + volumes: + - metabuilder-data:/app/data + - dbal-logs:/app/logs + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/health"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 30s + networks: + - metabuilder-network + + # Prometheus for metrics (optional) + prometheus: + image: prom/prometheus:latest + container_name: metabuilder-prometheus + ports: + - "9090:9090" + volumes: + - ./deployment/docker/prometheus.yml:/etc/prometheus/prometheus.yml:ro + - prometheus-data:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + - '--web.console.libraries=/usr/share/prometheus/console_libraries' + - '--web.console.templates=/usr/share/prometheus/consoles' + restart: unless-stopped + networks: + - metabuilder-network + profiles: + - monitoring + + # Grafana for visualization (optional) + grafana: + image: grafana/grafana:latest + container_name: metabuilder-grafana + ports: + - "3001:3000" + environment: + - GF_SECURITY_ADMIN_USER=admin + - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD:-admin} + - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource + volumes: + - grafana-data:/var/lib/grafana + - ./deployment/docker/grafana/dashboards:/etc/grafana/provisioning/dashboards:ro + - ./deployment/docker/grafana/datasources:/etc/grafana/provisioning/datasources:ro + depends_on: + - prometheus + restart: unless-stopped + networks: + - metabuilder-network + profiles: + - monitoring + +volumes: + metabuilder-data: + driver: local + dbal-logs: + driver: local + prometheus-data: + driver: local + grafana-data: + driver: local + +networks: + metabuilder-network: + driver: bridge diff --git a/docs/CONTAINER_IMAGES.md b/docs/CONTAINER_IMAGES.md new file mode 100644 index 000000000..ff60d5f84 --- /dev/null +++ b/docs/CONTAINER_IMAGES.md @@ -0,0 +1,189 @@ +# MetaBuilder Container Images + +MetaBuilder provides official container images hosted on GitHub Container Registry (GHCR) for easy deployment. + +## Available Images + +### 1. Next.js App (`ghcr.io/johndoe6345789/metabuilder/nextjs-app`) +The main MetaBuilder web application built with Next.js. + +**Features:** +- Multi-architecture support (amd64, arm64) +- Standalone output for minimal image size +- Built-in health checks +- Non-root user for security +- DBAL types pre-generated + +**Tags:** +- `latest` - Latest stable build from main branch +- `develop` - Latest development build +- `v*.*.*` - Semantic version tags +- `main-` - Specific commit from main branch + +### 2. DBAL Daemon (`ghcr.io/johndoe6345789/metabuilder/dbal-daemon`) +The secure C++ DBAL daemon for production deployments. + +**Features:** +- Multi-architecture support (amd64, arm64) +- Process isolation for security +- Connection pooling +- Row-level security enforcement + +## Quick Start + +### Using Docker Compose with GHCR Images + +```bash +# Pull and start all services +docker compose -f docker-compose.ghcr.yml up -d + +# With monitoring stack +docker compose -f docker-compose.ghcr.yml --profile monitoring up -d + +# Stop services +docker compose -f docker-compose.ghcr.yml down + +# View logs +docker compose -f docker-compose.ghcr.yml logs -f +``` + +### Running Individual Containers + +```bash +# Run Next.js app +docker run -d \ + --name metabuilder-nextjs \ + -p 3000:3000 \ + -e DATABASE_URL=file:/app/data/metabuilder.db \ + -v metabuilder-data:/app/data \ + ghcr.io/johndoe6345789/metabuilder/nextjs-app:latest + +# Run DBAL daemon +docker run -d \ + --name metabuilder-dbal \ + -p 8080:8080 \ + -p 50051:50051 \ + -e DATABASE_URL=file:/app/data/metabuilder.db \ + -v metabuilder-data:/app/data \ + ghcr.io/johndoe6345789/metabuilder/dbal-daemon:latest +``` + +## Authentication + +To pull images from GHCR, you need a GitHub Personal Access Token with `read:packages` scope: + +```bash +# Login to GHCR +echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin + +# Pull an image +docker pull ghcr.io/johndoe6345789/metabuilder/nextjs-app:latest +``` + +## Building Images Locally + +```bash +# Build Next.js app +docker build -f frontends/nextjs/Dockerfile -t metabuilder/nextjs-app:local . + +# Build with specific platform +docker buildx build \ + --platform linux/amd64,linux/arm64 \ + -f frontends/nextjs/Dockerfile \ + -t metabuilder/nextjs-app:local . +``` + +## Environment Variables + +### Next.js App +- `DATABASE_URL` - Database connection string +- `DBAL_API_URL` - DBAL daemon API URL (default: `http://localhost:8080`) +- `DBAL_WS_URL` - DBAL daemon WebSocket URL (default: `ws://localhost:50051`) +- `NEXTAUTH_SECRET` - NextAuth secret for session encryption +- `NODE_ENV` - Environment mode (production/development) + +### DBAL Daemon +- `DATABASE_URL` - Database connection string +- `LOG_LEVEL` - Logging level (debug/info/warn/error) +- `ENABLE_METRICS` - Enable Prometheus metrics (true/false) +- `MAX_CONNECTIONS` - Maximum database connections + +## Health Checks + +Both images include health checks: + +```bash +# Check Next.js app health +curl http://localhost:3000/api/health + +# Check DBAL daemon health +curl http://localhost:8080/health +``` + +## Security + +### Image Scanning +All images are automatically scanned for vulnerabilities using Trivy during the CI/CD pipeline. Results are available in the GitHub Security tab. + +### Attestations +Build provenance attestations are generated for all images pushed to GHCR, ensuring supply chain security. + +### Non-Root Users +All containers run as non-root users: +- Next.js app runs as user `nextjs` (UID 1001) +- DBAL daemon runs as user `dbal` (UID 1000) + +## Monitoring + +When using the monitoring profile: +- **Prometheus**: http://localhost:9090 +- **Grafana**: http://localhost:3001 (admin/admin) + +## Volumes + +- `metabuilder-data` - Persistent database and application data +- `dbal-logs` - DBAL daemon logs +- `prometheus-data` - Prometheus metrics storage +- `grafana-data` - Grafana dashboards and settings + +## Troubleshooting + +### Container won't start +```bash +# Check logs +docker logs metabuilder-nextjs +docker logs metabuilder-dbal + +# Check health status +docker inspect --format='{{json .State.Health}}' metabuilder-nextjs +``` + +### Permission issues +```bash +# Ensure volumes have correct permissions +docker volume inspect metabuilder-data +``` + +### Network connectivity +```bash +# Test network connectivity between containers +docker compose -f docker-compose.ghcr.yml exec nextjs-app curl http://dbal-daemon:8080/health +``` + +## CI/CD Integration + +Images are automatically built and pushed on: +- Push to `main` or `develop` branches +- New version tags (`v*.*.*`) +- Manual workflow dispatch + +See `.github/workflows/container-build.yml` for the complete workflow. + +## Support + +For issues related to container images, please open an issue in the MetaBuilder repository with: +- Image tag being used +- Docker/Podman version +- Platform (amd64/arm64) +- Container logs +- docker-compose.yml configuration (if applicable) diff --git a/frontends/nextjs/Dockerfile b/frontends/nextjs/Dockerfile new file mode 100644 index 000000000..35d1b28b6 --- /dev/null +++ b/frontends/nextjs/Dockerfile @@ -0,0 +1,89 @@ +# Multi-stage Dockerfile for MetaBuilder Next.js App +# Optimized for production deployment with GHCR + +# Stage 1: Dependencies +FROM node:20-alpine AS deps +WORKDIR /app + +# Install system dependencies +RUN apk add --no-cache libc6-compat + +# Copy package files for all workspaces +COPY package*.json ./ +COPY frontends/nextjs/package*.json ./frontends/nextjs/ +COPY dbal/development/package*.json ./dbal/development/ +COPY config/package*.json ./config/ 2>/dev/null || true + +# Install dependencies +RUN npm ci + +# Stage 2: Builder +FROM node:20-alpine AS builder +WORKDIR /app + +# Copy dependencies from deps stage +COPY --from=deps /app/node_modules ./node_modules +COPY --from=deps /app/frontends/nextjs/node_modules ./frontends/nextjs/node_modules +COPY --from=deps /app/dbal/development/node_modules ./dbal/development/node_modules + +# Copy source code +COPY . . + +# Generate DBAL types from YAML schemas +WORKDIR /app/dbal/development +RUN npx tsx ../shared/tools/codegen/generate-types.ts + +# Generate Prisma Client +WORKDIR /app/frontends/nextjs +RUN npm run db:generate + +# Build Next.js app +ENV NEXT_TELEMETRY_DISABLED=1 +ENV NODE_ENV=production +RUN npm run build + +# Stage 3: Production runner +FROM node:20-alpine AS runner +WORKDIR /app + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +# Install runtime dependencies +RUN apk add --no-cache \ + tini \ + curl \ + dumb-init + +# Create non-root user +RUN addgroup --system --gid 1001 nodejs && \ + adduser --system --uid 1001 nextjs + +# Copy necessary files from builder +COPY --from=builder /app/frontends/nextjs/public ./public +COPY --from=builder /app/frontends/nextjs/.next/standalone ./ +COPY --from=builder /app/frontends/nextjs/.next/static ./.next/static + +# Copy Prisma schema and generated client +COPY --from=builder /app/prisma ./prisma +COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma +COPY --from=builder /app/node_modules/@prisma ./node_modules/@prisma + +# Set proper permissions +RUN chown -R nextjs:nodejs /app + +USER nextjs + +EXPOSE 3000 + +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +# Health check +HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \ + CMD curl -f http://localhost:3000/api/health || exit 1 + +# Use dumb-init to handle signals +ENTRYPOINT ["/usr/bin/dumb-init", "--"] + +CMD ["node", "server.js"]