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 <noreply@anthropic.com>
This commit is contained in:
Till-JS 2026-01-23 14:23:49 +01:00
parent 294074f5f7
commit bb5f145286
5 changed files with 309 additions and 1 deletions

View file

@ -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

View file

@ -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"]

View file

@ -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 "$@"

View file

@ -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"]

View file

@ -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
# ============================================