From 190cffb61bba9df74906658d9d98384a6215591f Mon Sep 17 00:00:00 2001 From: William Valentin Date: Mon, 8 Sep 2025 21:24:20 -0700 Subject: [PATCH] refactor: simplify Docker and CI/CD to use unified config - Replace symlinked Dockerfile with simplified version in root - Reduce Docker build args (unified config provides defaults) - Update CI/CD workflows to use minimal build arguments - Add nginx.conf to root directory (replace docker/nginx.conf) - Remove docker-bake references from CI/CD workflows - Focus on essential runtime overrides only - Let unified config handle smart defaults --- .dockerignore | 114 ++++++++++++++++++++++++++++- .gitea/workflows/ci-cd.yml | 20 +---- .github/workflows/build-deploy.yml | 20 +---- Dockerfile | 82 ++++++++++++++++++++- nginx.conf | 49 +++++++++++++ 5 files changed, 251 insertions(+), 34 deletions(-) mode change 120000 => 100644 .dockerignore mode change 120000 => 100644 Dockerfile create mode 100644 nginx.conf diff --git a/.dockerignore b/.dockerignore deleted file mode 120000 index 66b30bc..0000000 --- a/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -docker/.dockerignore \ No newline at end of file diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d87ee8d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,113 @@ +# Node.js +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# Build outputs +dist/ +build/ +.cache/ + +# Environment files +.env +.env.local +.env.*.local + +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Git +.git/ +.gitignore + +# Documentation +README.md +*.md +docs/ + +# Test files +tests/ +test-results/ +coverage/ +playwright-report/ + +# Development files +.husky/ +.github/ +.gitea/ + +# Logs +logs/ +*.log + +# Runtime data +pids/ +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage/ +*.lcov + +# Dependency directories +jspm_packages/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# parcel-bundler cache (https://parceljs.org/) +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt + +# Storybook build outputs +.out +.storybook-out + +# Temporary folders +tmp/ +temp/ + +# Configuration files not needed in container +*.config.js +*.config.ts +!vite.config.ts +!jest.config.json diff --git a/.gitea/workflows/ci-cd.yml b/.gitea/workflows/ci-cd.yml index 6d76323..feb0e4c 100644 --- a/.gitea/workflows/ci-cd.yml +++ b/.gitea/workflows/ci-cd.yml @@ -47,31 +47,19 @@ jobs: - name: Build and push Docker image uses: docker/build-push-action@v5 with: - context: ./docker + context: . platforms: linux/amd64,linux/arm64 push: ${{ gitea.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: | - VITE_COUCHDB_URL=${{ vars.VITE_COUCHDB_URL || 'http://localhost:5984' }} - VITE_COUCHDB_USER=${{ vars.VITE_COUCHDB_USER || 'admin' }} - VITE_COUCHDB_PASSWORD=${{ secrets.VITE_COUCHDB_PASSWORD || 'change-this-secure-password' }} - APP_BASE_URL=${{ vars.APP_BASE_URL || 'http://localhost:8080' }} - VITE_GOOGLE_CLIENT_ID=${{ vars.VITE_GOOGLE_CLIENT_ID || '' }} - VITE_GITHUB_CLIENT_ID=${{ vars.VITE_GITHUB_CLIENT_ID || '' }} NODE_ENV=production + VITE_COUCHDB_URL=${{ vars.VITE_COUCHDB_URL }} + VITE_COUCHDB_USER=${{ vars.VITE_COUCHDB_USER }} + VITE_COUCHDB_PASSWORD=${{ secrets.VITE_COUCHDB_PASSWORD }} cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max - - name: Build with Bake (Alternative) - if: false # Set to true to use bake instead - uses: docker/bake-action@v4 - with: - workdir: ./docker - files: docker-bake.hcl - targets: prod - push: ${{ gitea.event_name != 'pull_request' }} - test: runs-on: ubuntu-latest container: diff --git a/.github/workflows/build-deploy.yml b/.github/workflows/build-deploy.yml index a982367..12b53a6 100644 --- a/.github/workflows/build-deploy.yml +++ b/.github/workflows/build-deploy.yml @@ -45,31 +45,19 @@ jobs: - name: Build and push Docker image uses: docker/build-push-action@v5 with: - context: ./docker + context: . platforms: linux/amd64,linux/arm64 push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: | - VITE_COUCHDB_URL=${{ vars.VITE_COUCHDB_URL || 'http://localhost:5984' }} - VITE_COUCHDB_USER=${{ vars.VITE_COUCHDB_USER || 'admin' }} - VITE_COUCHDB_PASSWORD=${{ secrets.VITE_COUCHDB_PASSWORD || 'change-this-secure-password' }} - APP_BASE_URL=${{ vars.APP_BASE_URL || 'http://localhost:8080' }} - VITE_GOOGLE_CLIENT_ID=${{ vars.VITE_GOOGLE_CLIENT_ID || '' }} - VITE_GITHUB_CLIENT_ID=${{ vars.VITE_GITHUB_CLIENT_ID || '' }} NODE_ENV=production + VITE_COUCHDB_URL=${{ vars.VITE_COUCHDB_URL }} + VITE_COUCHDB_USER=${{ vars.VITE_COUCHDB_USER }} + VITE_COUCHDB_PASSWORD=${{ secrets.VITE_COUCHDB_PASSWORD }} cache-from: type=gha cache-to: type=gha,mode=max - - name: Build with Bake (Alternative) - if: false # Set to true to use bake instead - uses: docker/bake-action@v4 - with: - workdir: ./docker - files: docker-bake.hcl - targets: prod - push: ${{ github.event_name != 'pull_request' }} - test: runs-on: ubuntu-latest needs: build diff --git a/Dockerfile b/Dockerfile deleted file mode 120000 index 93c313e..0000000 --- a/Dockerfile +++ /dev/null @@ -1 +0,0 @@ -docker/Dockerfile \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2b2ff76 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,81 @@ +# Multi-stage Dockerfile for Medication Reminder App +FROM node:20-slim AS base + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# Install Bun +RUN curl -fsSL https://bun.sh/install | bash +ENV PATH="/root/.bun/bin:$PATH" + +# Set working directory +WORKDIR /app + +# Create non-root user +RUN groupadd --gid 1001 nodeuser && \ + useradd --uid 1001 --gid nodeuser --shell /bin/bash --create-home nodeuser + +# Builder stage +FROM base AS builder + +# Copy package files +COPY --chown=nodeuser:nodeuser package.json bun.lock* ./ + +# Install dependencies +RUN bun install --frozen-lockfile + +# Copy source code +COPY --chown=nodeuser:nodeuser . ./ + +# Build arguments for environment configuration +# Build Environment - unified config will handle the rest +ARG NODE_ENV=production + +# Only essential runtime variables that override unified config defaults +ARG VITE_COUCHDB_URL +ARG VITE_COUCHDB_USER +ARG VITE_COUCHDB_PASSWORD + +# Set environment variables for build process +# Unified config handles defaults, only set essential runtime overrides +ENV NODE_ENV=$NODE_ENV +ENV VITE_COUCHDB_URL=$VITE_COUCHDB_URL +ENV VITE_COUCHDB_USER=$VITE_COUCHDB_USER +ENV VITE_COUCHDB_PASSWORD=$VITE_COUCHDB_PASSWORD +ENV NODE_ENV=$NODE_ENV + +# Build the application +RUN bun run build + +# Production stage +FROM nginx:alpine AS production + +# Install curl for health checks +RUN apk add --no-cache curl + +# Copy built files from builder stage +COPY --from=builder /app/dist /usr/share/nginx/html + +# Copy nginx configuration +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# Set proper permissions for nginx +RUN chown -R nginx:nginx /usr/share/nginx/html && \ + chown -R nginx:nginx /var/cache/nginx && \ + chown -R nginx:nginx /var/log/nginx && \ + chown -R nginx:nginx /etc/nginx/conf.d + +# Switch to nginx user +USER nginx + +# Expose port +EXPOSE 80 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost/health || exit 1 + +# Start nginx +CMD ["nginx", "-g", "daemon off;"] diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..a68a810 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,49 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Enable gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied expired no-cache no-store private must-revalidate auth; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/javascript + application/xml+rss + application/json; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + + # Handle React Router (SPA) + location / { + try_files $uri $uri/ /index.html; + } + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + # Disable access to hidden files + location ~ /\. { + deny all; + } +}