From bb5f14528612adccc3a8880f0c113ad32a1e981b Mon Sep 17 00:00:00 2001 From: Till-JS <101404291+Till-JS@users.noreply.github.com> Date: Fri, 23 Jan 2026 14:23:49 +0100 Subject: [PATCH] feat(contacts): add Docker deployment for Mac Mini - Add Dockerfile for contacts-backend (port 3015) - Add Dockerfile for contacts-web (port 5184) - Add docker-entrypoint.sh for database migrations - Update CI workflow with contacts-backend and contacts-web build jobs - Add contacts services to docker-compose.macmini.yml - Update CORS origins to include contacts.mana.how Co-Authored-By: Claude Opus 4.5 --- .github/workflows/ci.yml | 82 +++++++++++++++++ apps/contacts/apps/backend/Dockerfile | 71 +++++++++++++++ .../apps/backend/docker-entrypoint.sh | 9 ++ apps/contacts/apps/web/Dockerfile | 90 +++++++++++++++++++ docker-compose.macmini.yml | 58 +++++++++++- 5 files changed, 309 insertions(+), 1 deletion(-) create mode 100644 apps/contacts/apps/backend/Dockerfile create mode 100644 apps/contacts/apps/backend/docker-entrypoint.sh create mode 100644 apps/contacts/apps/web/Dockerfile diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d6b96290d..c220f1cd6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,6 +59,8 @@ jobs: calendar-web: ${{ steps.changes.outputs.calendar-web }} clock-backend: ${{ steps.changes.outputs.clock-backend }} clock-web: ${{ steps.changes.outputs.clock-web }} + contacts-backend: ${{ steps.changes.outputs.contacts-backend }} + contacts-web: ${{ steps.changes.outputs.contacts-web }} any-changes: ${{ steps.changes.outputs.any-changes }} steps: - name: Checkout code @@ -82,6 +84,8 @@ jobs: echo "calendar-web=true" >> $GITHUB_OUTPUT echo "clock-backend=true" >> $GITHUB_OUTPUT echo "clock-web=true" >> $GITHUB_OUTPUT + echo "contacts-backend=true" >> $GITHUB_OUTPUT + echo "contacts-web=true" >> $GITHUB_OUTPUT echo "any-changes=true" >> $GITHUB_OUTPUT exit 0 fi @@ -109,6 +113,8 @@ jobs: echo "calendar-web=true" >> $GITHUB_OUTPUT echo "clock-backend=true" >> $GITHUB_OUTPUT echo "clock-web=true" >> $GITHUB_OUTPUT + echo "contacts-backend=true" >> $GITHUB_OUTPUT + echo "contacts-web=true" >> $GITHUB_OUTPUT echo "any-changes=true" >> $GITHUB_OUTPUT exit 0 fi @@ -220,6 +226,22 @@ jobs: echo "clock-web=false" >> $GITHUB_OUTPUT fi + # contacts-backend + CONTACTS_BACKEND_CHANGED=$(check_pattern "apps/contacts/apps/backend/") + if [ "$COMMON_CHANGED" == "true" ] || [ "$SHARED_AUTH_CHANGED" == "true" ] || [ "$CONTACTS_BACKEND_CHANGED" == "true" ]; then + echo "contacts-backend=true" >> $GITHUB_OUTPUT + else + echo "contacts-backend=false" >> $GITHUB_OUTPUT + fi + + # contacts-web + CONTACTS_WEB_CHANGED=$(check_pattern "apps/contacts/apps/web/") + if [ "$COMMON_CHANGED" == "true" ] || [ "$SHARED_AUTH_CHANGED" == "true" ] || [ "$SHARED_UI_CHANGED" == "true" ] || [ "$SHARED_WEB_CHANGED" == "true" ] || [ "$CONTACTS_WEB_CHANGED" == "true" ]; then + echo "contacts-web=true" >> $GITHUB_OUTPUT + else + echo "contacts-web=false" >> $GITHUB_OUTPUT + fi + # Check if any service needs building if grep -q "=true" $GITHUB_OUTPUT; then echo "any-changes=true" >> $GITHUB_OUTPUT @@ -243,6 +265,8 @@ jobs: echo "| calendar-web | ${{ steps.changes.outputs.calendar-web }} |" >> $GITHUB_STEP_SUMMARY echo "| clock-backend | ${{ steps.changes.outputs.clock-backend }} |" >> $GITHUB_STEP_SUMMARY echo "| clock-web | ${{ steps.changes.outputs.clock-web }} |" >> $GITHUB_STEP_SUMMARY + echo "| contacts-backend | ${{ steps.changes.outputs.contacts-backend }} |" >> $GITHUB_STEP_SUMMARY + echo "| contacts-web | ${{ steps.changes.outputs.contacts-web }} |" >> $GITHUB_STEP_SUMMARY # =========================================== # Validation job - runs on PRs @@ -571,3 +595,61 @@ jobs: tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max + + build-contacts-backend: + name: Build contacts-backend + runs-on: ubuntu-latest + needs: detect-changes + if: needs.detect-changes.outputs.contacts-backend == 'true' + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-qemu-action@v3 + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - uses: docker/metadata-action@v5 + id: meta + with: + images: ghcr.io/${{ github.repository_owner }}/contacts-backend + tags: type=raw,value=latest + - uses: docker/build-push-action@v5 + with: + context: . + file: apps/contacts/apps/backend/Dockerfile + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + cache-from: type=gha + cache-to: type=gha,mode=max + + build-contacts-web: + name: Build contacts-web + runs-on: ubuntu-latest + needs: detect-changes + if: needs.detect-changes.outputs.contacts-web == 'true' + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-qemu-action@v3 + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - uses: docker/metadata-action@v5 + id: meta + with: + images: ghcr.io/${{ github.repository_owner }}/contacts-web + tags: type=raw,value=latest + - uses: docker/build-push-action@v5 + with: + context: . + file: apps/contacts/apps/web/Dockerfile + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/apps/contacts/apps/backend/Dockerfile b/apps/contacts/apps/backend/Dockerfile new file mode 100644 index 000000000..a67ba33b8 --- /dev/null +++ b/apps/contacts/apps/backend/Dockerfile @@ -0,0 +1,71 @@ +# Build stage +FROM node:20-alpine AS builder + +# Install pnpm +RUN corepack enable && corepack prepare pnpm@9.15.0 --activate + +WORKDIR /app + +# Copy root workspace files +COPY pnpm-workspace.yaml ./ +COPY package.json ./ +COPY pnpm-lock.yaml ./ + +# Copy shared packages +COPY packages/shared-errors ./packages/shared-errors +COPY packages/shared-nestjs-auth ./packages/shared-nestjs-auth +COPY packages/shared-storage ./packages/shared-storage + +# Copy contacts backend +COPY apps/contacts/apps/backend ./apps/contacts/apps/backend + +# Install dependencies +RUN pnpm install --frozen-lockfile + +# Build shared packages first +WORKDIR /app/packages/shared-errors +RUN pnpm build + +WORKDIR /app/packages/shared-nestjs-auth +RUN pnpm build + +WORKDIR /app/packages/shared-storage +RUN pnpm build + +# Build the backend +WORKDIR /app/apps/contacts/apps/backend +RUN pnpm build + +# Production stage +FROM node:20-alpine AS production + +# Install pnpm and postgresql-client for health checks +RUN corepack enable && corepack prepare pnpm@9.15.0 --activate \ + && apk add --no-cache postgresql-client + +WORKDIR /app + +# Copy everything from builder (including node_modules) +COPY --from=builder /app/pnpm-workspace.yaml ./ +COPY --from=builder /app/package.json ./ +COPY --from=builder /app/pnpm-lock.yaml ./ +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/packages ./packages +COPY --from=builder /app/apps/contacts ./apps/contacts + +# Copy entrypoint script +COPY apps/contacts/apps/backend/docker-entrypoint.sh /usr/local/bin/ +RUN chmod +x /usr/local/bin/docker-entrypoint.sh + +WORKDIR /app/apps/contacts/apps/backend + +# Expose port +EXPOSE 3015 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:3015/api/v1/health || exit 1 + +# Run entrypoint script +ENTRYPOINT ["docker-entrypoint.sh"] +CMD ["node", "dist/main.js"] diff --git a/apps/contacts/apps/backend/docker-entrypoint.sh b/apps/contacts/apps/backend/docker-entrypoint.sh new file mode 100644 index 000000000..ee365a597 --- /dev/null +++ b/apps/contacts/apps/backend/docker-entrypoint.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -e + +echo "📋 Running database migrations..." +npx drizzle-kit push --config drizzle.config.ts --force || echo "⚠️ Migration failed, continuing anyway..." + +# Start the application +echo "🚀 Starting Contacts Backend..." +exec "$@" diff --git a/apps/contacts/apps/web/Dockerfile b/apps/contacts/apps/web/Dockerfile new file mode 100644 index 000000000..bab73471a --- /dev/null +++ b/apps/contacts/apps/web/Dockerfile @@ -0,0 +1,90 @@ +# Build stage +FROM node:20-alpine AS builder + +# Build arguments for SvelteKit static env vars +ARG PUBLIC_BACKEND_URL=http://contacts-backend:3015 +ARG PUBLIC_MANA_CORE_AUTH_URL=http://mana-core-auth:3001 + +# Set as environment variables for build +ENV PUBLIC_BACKEND_URL=$PUBLIC_BACKEND_URL +ENV PUBLIC_MANA_CORE_AUTH_URL=$PUBLIC_MANA_CORE_AUTH_URL + +# Install pnpm +RUN corepack enable && corepack prepare pnpm@9.15.0 --activate + +WORKDIR /app + +# Copy root workspace files +COPY pnpm-workspace.yaml ./ +COPY package.json ./ +COPY pnpm-lock.yaml ./ + +# Copy shared packages needed by contacts web +COPY packages/shared-types ./packages/shared-types +COPY packages/shared-auth ./packages/shared-auth +COPY packages/shared-auth-ui ./packages/shared-auth-ui +COPY packages/shared-branding ./packages/shared-branding +COPY packages/shared-feedback-service ./packages/shared-feedback-service +COPY packages/shared-feedback-types ./packages/shared-feedback-types +COPY packages/shared-feedback-ui ./packages/shared-feedback-ui +COPY packages/shared-help-content ./packages/shared-help-content +COPY packages/shared-help-types ./packages/shared-help-types +COPY packages/shared-help-ui ./packages/shared-help-ui +COPY packages/shared-i18n ./packages/shared-i18n +COPY packages/shared-icons ./packages/shared-icons +COPY packages/shared-tailwind ./packages/shared-tailwind +COPY packages/shared-theme ./packages/shared-theme +COPY packages/shared-theme-ui ./packages/shared-theme-ui +COPY packages/shared-subscription-types ./packages/shared-subscription-types +COPY packages/shared-subscription-ui ./packages/shared-subscription-ui +COPY packages/shared-profile-ui ./packages/shared-profile-ui +COPY packages/shared-ui ./packages/shared-ui +COPY packages/shared-utils ./packages/shared-utils +COPY packages/shared-tags ./packages/shared-tags +COPY packages/shared-splitscreen ./packages/shared-splitscreen + +# Copy contacts web +COPY apps/contacts/apps/web ./apps/contacts/apps/web + +# Install dependencies +RUN pnpm install --frozen-lockfile + +# Build shared packages that need building +WORKDIR /app/packages/shared-auth +RUN pnpm build || true + +# Build the web app +WORKDIR /app/apps/contacts/apps/web +RUN pnpm exec svelte-kit sync +RUN pnpm build + +# Production stage +FROM node:20-alpine AS production + +# Keep same directory structure as builder so pnpm symlinks resolve correctly +WORKDIR /app/apps/contacts/apps/web + +# Copy the pnpm store that symlinks point to (at /app/node_modules/.pnpm) +COPY --from=builder /app/node_modules/.pnpm /app/node_modules/.pnpm + +# Copy the app's node_modules (contains symlinks to the pnpm store) +COPY --from=builder /app/apps/contacts/apps/web/node_modules ./node_modules + +# Copy built application +COPY --from=builder /app/apps/contacts/apps/web/build ./build +COPY --from=builder /app/apps/contacts/apps/web/package.json ./ + +# Expose port +EXPOSE 5184 + +# Set environment variables +ENV NODE_ENV=production +ENV PORT=5184 +ENV HOST=0.0.0.0 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:5184/health || exit 1 + +# Run the app +CMD ["node", "build"] diff --git a/docker-compose.macmini.yml b/docker-compose.macmini.yml index 1119e5492..312103d76 100644 --- a/docker-compose.macmini.yml +++ b/docker-compose.macmini.yml @@ -63,7 +63,7 @@ services: BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:-${JWT_SECRET:-your-jwt-secret-change-me}} JWT_PUBLIC_KEY: ${JWT_PUBLIC_KEY:-} JWT_PRIVATE_KEY: ${JWT_PRIVATE_KEY:-} - CORS_ORIGINS: https://mana.how,https://chat.mana.how,https://todo.mana.how,https://calendar.mana.how,https://clock.mana.how + CORS_ORIGINS: https://mana.how,https://chat.mana.how,https://todo.mana.how,https://calendar.mana.how,https://clock.mana.how,https://contacts.mana.how ports: - "3001:3001" healthcheck: @@ -95,6 +95,8 @@ services: PUBLIC_CALENDAR_API_URL_CLIENT: https://calendar-api.mana.how PUBLIC_CLOCK_API_URL: http://clock-backend:3017 PUBLIC_CLOCK_API_URL_CLIENT: https://clock-api.mana.how + PUBLIC_CONTACTS_API_URL: http://contacts-backend:3015 + PUBLIC_CONTACTS_API_URL_CLIENT: https://contacts-api.mana.how ports: - "5173:5173" healthcheck: @@ -322,6 +324,60 @@ services: retries: 3 start_period: 40s + # ============================================ + # Contacts App + # ============================================ + + contacts-backend: + image: ghcr.io/memo-2023/contacts-backend:latest + container_name: contacts-backend + restart: always + depends_on: + mana-core-auth: + condition: service_healthy + postgres: + condition: service_healthy + environment: + NODE_ENV: production + PORT: 3015 + DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:-manacore123}@postgres:5432/contacts + DB_HOST: postgres + DB_PORT: 5432 + DB_USER: postgres + MANA_CORE_AUTH_URL: http://mana-core-auth:3001 + CORS_ORIGINS: https://contacts.mana.how,https://mana.how,https://calendar.mana.how + ports: + - "3015:3015" + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3015/api/v1/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + + contacts-web: + image: ghcr.io/memo-2023/contacts-web:latest + container_name: contacts-web + restart: always + depends_on: + contacts-backend: + condition: service_healthy + environment: + NODE_ENV: production + PORT: 5184 + PUBLIC_BACKEND_URL: http://contacts-backend:3015 + PUBLIC_MANA_CORE_AUTH_URL: http://mana-core-auth:3001 + PUBLIC_BACKEND_URL_CLIENT: https://contacts-api.mana.how + PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.mana.how + ports: + - "5184:5184" + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5184/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + # ============================================ # Volumes # ============================================