# Docker Production Build Guide

## Goal
Build a production-ready Docker image containing both the frontend (React/Vite) and backend (Express/Node.js) that connects to an external MySQL database. The container will serve the frontend on port 5173 and proxy API requests to the backend running on localhost:3000 inside the container.

**SUCCESS CRITERIA**: Docker image is built successfully and ready for deployment.

## Architecture
- **Single Docker Container**: Contains both FE and BE
- **External Database**: MySQL server (not in container)
- **Port Exposure**: Only port 5173 (frontend) exposed
- **Internal Communication**: FE → BE via localhost:3000 inside container
- **Reverse Proxy**: Caddy handles SSL/domain externally (Unraid setup)

---

## Task 1: Create Dockerfile

**File**: `Dockerfile` (in project root)

### Requirements:
1. **Multi-stage build** for optimal image size
2. **Stage 1 - Build Frontend**:
   - Use `node:20-alpine` as base
   - Set working directory to `/app`
   - Copy `package.json`, `pnpm-lock.yaml`, `pnpm-workspace.yaml`
   - Copy `packages/frontend/package.json`
   - Install pnpm globally: `npm install -g pnpm`
   - Install dependencies: `pnpm install --frozen-lockfile`
   - Copy entire `packages/frontend` directory
   - Build frontend: `cd packages/frontend && pnpm build`
   - Output will be in `packages/frontend/dist`

3. **Stage 2 - Build Backend**:
   - Use `node:20-alpine` as base
   - Set working directory to `/app`
   - Copy workspace files (same as stage 1)
   - Install pnpm globally
   - Install dependencies: `pnpm install --frozen-lockfile`
   - Copy entire `packages/backend` directory
   - Generate Prisma client: `cd packages/backend && npx prisma generate`
   - Build backend: `cd packages/backend && pnpm build`
   - Output will be in `packages/backend/dist`

4. **Stage 3 - Production Runtime**:
   - Use `node:20-alpine` as base
   - Install pnpm globally and `serve` package globally: `npm install -g pnpm serve`
   - Set working directory to `/app`
   - Copy production dependencies setup:
     - Copy `package.json`, `pnpm-workspace.yaml`, `pnpm-lock.yaml`
     - Copy `packages/backend/package.json` to `packages/backend/`
     - Copy `packages/frontend/package.json` to `packages/frontend/`
   - Install ONLY production dependencies: `pnpm install --prod --frozen-lockfile`
   - Copy backend build artifacts:
     - From stage 2: `packages/backend/dist` → `/app/packages/backend/dist`
     - From stage 2: `packages/backend/prisma` → `/app/packages/backend/prisma`
     - From stage 2: `packages/backend/node_modules/.prisma` → `/app/packages/backend/node_modules/.prisma`
   - Copy frontend build artifacts:
     - From stage 1: `packages/frontend/dist` → `/app/packages/frontend/dist`
   - Create uploads directory: `mkdir -p /app/packages/backend/uploads`
   - Expose port 5173
   - Create startup script (see Task 2)
   - Set CMD to run startup script

### Important Notes:
- Use `.dockerignore` to exclude `node_modules`, `dist`, `.env` files
- Ensure Prisma client is generated and copied correctly
- Backend needs access to Prisma schema for migrations

---

## Task 2: Create Startup Script

**File**: `docker-entrypoint.sh` (in project root)

### Requirements:
1. **Bash script** with proper shebang: `#!/bin/sh`
2. **Set error handling**: `set -e`
3. **Environment validation**:
   - Check required env vars: `DATABASE_URL`, `SESSION_SECRET`
   - Exit with error message if missing
4. **Database migration**:
   - Navigate to backend: `cd /app/packages/backend`
   - Run Prisma migrations: `npx prisma migrate deploy`
   - Optional: Run seed if `RUN_SEED=true` env var is set
5. **Start backend**:
   - Start backend in background: `node dist/server.js &`
   - Store PID: `BACKEND_PID=$!`
6. **Wait for backend**:
   - Add 5-second sleep or health check loop
   - Ensure backend is ready before starting frontend
7. **Start frontend**:
   - Serve frontend on port 5173: `serve -s /app/packages/frontend/dist -l 5173`
8. **Signal handling**:
   - Trap SIGTERM/SIGINT to gracefully shutdown both processes
   - Kill backend PID on exit

### Script Template:
```bash
#!/bin/sh
set -e

echo "🚀 Starting Znakovni.hr Production Container..."

# Validate environment
if [ -z "$DATABASE_URL" ]; then
  echo "❌ ERROR: DATABASE_URL is required"
  exit 1
fi

# Run migrations
cd /app/packages/backend
echo "📦 Running database migrations..."
npx prisma migrate deploy

# Start backend
echo "🔧 Starting backend server..."
cd /app/packages/backend
node dist/server.js &
BACKEND_PID=$!

# Wait for backend
sleep 5

# Start frontend
echo "🎨 Starting frontend server..."
cd /app
serve -s packages/frontend/dist -l 5173 -n &
FRONTEND_PID=$!

# Trap signals
trap "kill $BACKEND_PID $FRONTEND_PID" SIGTERM SIGINT

# Wait for processes
wait $BACKEND_PID $FRONTEND_PID
```

---

## Task 3: Create .dockerignore

**File**: `.dockerignore` (in project root)

### Exclude:
```
node_modules
dist
.env
.env.local
.env.*.local
*.log
.git
.gitignore
.vscode
.idea
*.md
!README.md
packages/*/node_modules
packages/*/dist
packages/backend/uploads/*
!packages/backend/uploads/.gitkeep
.DS_Store
```

---

## Task 4: Update Frontend Build Configuration

**File**: `packages/frontend/vite.config.ts`

### Changes Required:
1. **Remove hardcoded host**: Change `host: '192.168.1.238'` to `host: '0.0.0.0'` or remove entirely
2. **Production build**: Ensure build outputs to `dist` directory
3. **API proxy**: Remove proxy config (not needed in production, handled by env var)

### Updated Config:
```typescript
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
  server: {
    host: '0.0.0.0',
    port: 5173,
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
      },
    },
  },
});
```

---

## Task 5: Update Frontend API Configuration

**File**: `packages/frontend/.env.production` (create new file)

### Content:
```env
VITE_API_URL=http://localhost:3000
```

**Note**: In production Docker container, frontend connects to backend via localhost since they're in the same container.

---

## Task 6: Update Backend Server Configuration

**File**: `packages/backend/src/server.ts`

### Verify/Update:
1. **HOST binding**: Ensure `HOST = process.env.HOST || '0.0.0.0'` (line 85)
2. **PORT**: Ensure `PORT = parseInt(process.env.PORT || '3000', 10)` (line 16)
3. **CORS origin**: Should accept `FRONTEND_URL` from env (line 24)
4. **Static uploads**: Ensure uploads path is correct (line 57-58)

### No changes needed if these are already correct (they are based on codebase retrieval).

---

## Task 7: Create Docker Compose Example

**File**: `docker-compose.yml` (in project root)

### Purpose:
Provide example for running the container with proper environment variables.

### Content:
```yaml
version: '3.8'

services:
  znakovni:
    build:
      context: .
      dockerfile: Dockerfile
    image: znakovni:latest
    container_name: znakovni-app
    restart: unless-stopped
    ports:
      - "5173:5173"
    environment:
      # Database Configuration
      DATABASE_URL: "mysql://user:password@192.168.1.74:3306/znakovni"
      # Optional: Shadow database for Prisma migrations
      # SHADOW_DATABASE_URL: "mysql://user:password@192.168.1.74:3306/znakovni_shadow"

      # Server Configuration
      NODE_ENV: production
      PORT: 3000
      HOST: 0.0.0.0
      FRONTEND_URL: http://localhost:5173

      # Session Secret (CHANGE THIS!)
      SESSION_SECRET: your-super-secret-session-key-change-in-production-min-32-chars

      # OAuth - Google (optional, configure if using)
      # GOOGLE_CLIENT_ID: your-google-client-id
      # GOOGLE_CLIENT_SECRET: your-google-client-secret
      # GOOGLE_CALLBACK_URL: https://yourdomain.com/api/auth/google/callback

      # OAuth - Microsoft (optional, configure if using)
      # MICROSOFT_CLIENT_ID: your-microsoft-client-id
      # MICROSOFT_CLIENT_SECRET: your-microsoft-client-secret
      # MICROSOFT_CALLBACK_URL: https://yourdomain.com/api/auth/microsoft/callback

      # File Upload
      UPLOAD_DIR: ./uploads
      MAX_FILE_SIZE: 104857600

      # Optional: Run database seed on startup
      # RUN_SEED: "false"

    volumes:
      # Persist uploaded files
      - ./uploads:/app/packages/backend/uploads

    networks:
      - znakovni-network

    # Health check
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/api/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

networks:
  znakovni-network:
    driver: bridge
```

---

## Task 8: Create Production Environment Template

**File**: `.env.production.example` (in project root)

### Content:
```env
# ===========================================
# ZNAKOVNI.HR PRODUCTION CONFIGURATION
# ===========================================

# Database Configuration (REQUIRED)
# Point to your external MySQL server
DATABASE_URL="mysql://username:password@192.168.1.74:3306/znakovni"

# Optional: Shadow database for Prisma migrations
# SHADOW_DATABASE_URL="mysql://username:password@192.168.1.74:3306/znakovni_shadow"

# Server Configuration
NODE_ENV=production
PORT=3000
HOST=0.0.0.0
FRONTEND_URL=http://localhost:5173

# Session Secret (REQUIRED - CHANGE THIS!)
# Generate with: openssl rand -base64 32
SESSION_SECRET=CHANGE-THIS-TO-A-RANDOM-STRING-MIN-32-CHARACTERS-LONG

# OAuth - Google (Optional)
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URL=https://yourdomain.com/api/auth/google/callback

# OAuth - Microsoft (Optional)
MICROSOFT_CLIENT_ID=
MICROSOFT_CLIENT_SECRET=
MICROSOFT_CALLBACK_URL=https://yourdomain.com/api/auth/microsoft/callback

# File Upload Configuration
UPLOAD_DIR=./uploads
MAX_FILE_SIZE=104857600

# Database Seeding (Optional)
# Set to "true" to run seed on container startup
RUN_SEED=false
```

---

## Task 9: Build Docker Image

### Build Command:
```bash
# From project root
docker build -t znakovni:latest .
```

### Verify Build Success:
```bash
# Check image exists
docker images | grep znakovni

# Should show:
# znakovni    latest    <image-id>    <time>    <size>
```

### Expected Build Output:
- ✅ Stage 1: Frontend build completes successfully
- ✅ Stage 2: Backend build completes successfully
- ✅ Stage 3: Production image created
- ✅ Image tagged as `znakovni:latest`
- ✅ No build errors

**TASK COMPLETE** when image is built and shows in `docker images` output.

---

## Task 10: Optional - Test Container Locally

**NOTE**: This task is OPTIONAL. The main goal is to build the image. User will handle deployment.

### Run Container (Manual):
```bash
docker run -d \
  --name znakovni-app \
  -p 5173:5173 \
  -e DATABASE_URL="mysql://user:password@192.168.1.74:3306/znakovni" \
  -e SESSION_SECRET="your-secret-key-here" \
  -e NODE_ENV=production \
  -v $(pwd)/uploads:/app/packages/backend/uploads \
  znakovni:latest
```

### Run with Docker Compose:
```bash
# Edit docker-compose.yml with your database credentials
docker-compose up -d

# View logs
docker-compose logs -f

# Stop
docker-compose down
```

### Verify Container:
```bash
# Check if container is running
docker ps

# Check logs
docker logs znakovni-app

# Test backend health
curl http://localhost:5173/api/health

# Test frontend
curl http://localhost:5173
```

---

## Task 11: Save Docker Image for Deployment

**NOTE**: This task is for preparing the image for transfer to Unraid. User will handle actual deployment.

### Save Image to File:
```bash
# Save image to tar file
docker save znakovni:latest -o znakovni.tar

# Verify file created
ls -lh znakovni.tar
```

### Image is Ready for Deployment
The `znakovni.tar` file can now be:
- Transferred to Unraid server
- Loaded with: `docker load -i znakovni.tar`
- Deployed by user

---

## DEPLOYMENT REFERENCE (For User)

### Unraid Deployment Steps:
1. **Transfer image**: `scp znakovni.tar user@unraid:/mnt/user/appdata/`
2. **Load image**: `docker load -i znakovni.tar`
3. **Create container** in Unraid UI:
   - **Repository**: `znakovni:latest`
   - **Port**: `5173` → `5173` (or any host port)
   - **Environment Variables**: Add all required vars from `.env.production.example`
   - **Volume**: Map host path to `/app/packages/backend/uploads`
4. **Configure Caddy** reverse proxy:
   - Point domain to `http://unraid-ip:5173`
   - Caddy handles SSL/HTTPS

### Caddy Configuration Example:
```
znakovni.yourdomain.com {
    reverse_proxy http://localhost:5173
}
```

---

## Task 12: Troubleshooting Build Issues

### Build-Time Issues:

1. **pnpm install fails**:
   - Ensure `pnpm-lock.yaml` is present
   - Check Node.js version compatibility (needs Node 20)
   - Verify `pnpm-workspace.yaml` is copied correctly

2. **Frontend build fails**:
   - Check TypeScript compilation errors
   - Verify all dependencies are installed
   - Ensure Vite config is correct
   - Check for missing environment variables

3. **Backend build fails**:
   - Check TypeScript compilation errors
   - Verify Prisma schema is valid
   - Ensure all dependencies are installed

4. **Prisma generate fails**:
   - Verify `DATABASE_URL` format is correct (even for build)
   - Check Prisma schema syntax
   - Ensure MySQL provider is specified

5. **Docker build context too large**:
   - Verify `.dockerignore` is present and correct
   - Ensure `node_modules` and `dist` are excluded

### Debug Build:
```bash
# Build with no cache to see all steps
docker build --no-cache -t znakovni:latest .

# Build with progress output
docker build --progress=plain -t znakovni:latest .

# Check build context size
docker build --no-cache -t znakovni:latest . 2>&1 | grep "Sending build context"
```

### Runtime Issues (If Testing Container):

1. **Database Connection Failed**:
   - Verify `DATABASE_URL` is correct
   - Ensure MySQL server allows connections from Docker container IP
   - Check MySQL user permissions: `GRANT ALL ON znakovni.* TO 'user'@'%';`

2. **Frontend Can't Reach Backend**:
   - Verify `VITE_API_URL=http://localhost:3000` in frontend build
   - Check backend is listening on `0.0.0.0:3000`
   - Verify CORS settings in backend

3. **Container Exits Immediately**:
   - Check logs: `docker logs znakovni-app`
   - Verify all required env vars are set
   - Ensure database is accessible

### Debug Commands (For Running Container):
```bash
# Enter running container
docker exec -it znakovni-app sh

# Check backend process
docker exec znakovni-app ps aux | grep node

# Check environment variables
docker exec znakovni-app env | grep DATABASE
```

---

## REFERENCE: Security Checklist (For User - Deployment Phase)

Before deploying to production:

- [ ] Change `SESSION_SECRET` to a strong random string (min 32 chars)
- [ ] Use strong MySQL password
- [ ] Configure OAuth credentials if using Google/Microsoft login
- [ ] Update OAuth callback URLs to production domain
- [ ] Ensure MySQL server has firewall rules (only allow necessary IPs)
- [ ] Set up regular database backups
- [ ] Configure Caddy with proper SSL certificates
- [ ] Review and restrict MySQL user permissions
- [ ] Set up monitoring/logging for the container
- [ ] Configure automatic container restarts (`restart: unless-stopped`)

---

## Summary

This setup creates a **fully self-contained Docker image** with:
- ✅ Frontend (React/Vite) built and served on port 5173
- ✅ Backend (Express/Node.js) running on internal port 3000
- ✅ Prisma ORM with automatic migrations on startup
- ✅ External MySQL database connection
- ✅ Persistent uploads via Docker volumes
- ✅ Health checks and graceful shutdown
- ✅ Production-optimized multi-stage build
- ✅ Ready for Unraid + Caddy deployment

---

## IMPLEMENTATION PLAN FOR AI AGENTS

### Phase 1: Create Required Files (Tasks 1-8)
1. ✅ Create `Dockerfile` with multi-stage build
2. ✅ Create `docker-entrypoint.sh` startup script
3. ✅ Create `.dockerignore` file
4. ✅ Update `packages/frontend/vite.config.ts`
5. ✅ Create `packages/frontend/.env.production`
6. ✅ Verify `packages/backend/src/server.ts` configuration
7. ✅ Create `docker-compose.yml` example
8. ✅ Create `.env.production.example` template

### Phase 2: Build Docker Image (Task 9)
9. ✅ Run `docker build -t znakovni:latest .`
10. ✅ Verify image exists with `docker images | grep znakovni`

### Phase 3: Optional - Save Image (Task 11)
11. ⚠️ OPTIONAL: Save image to tar file for transfer

### SUCCESS CRITERIA:
**✅ COMPLETE** when `docker build` succeeds and image `znakovni:latest` exists.

User will handle deployment to Unraid.

---

## Quick Reference: Files to Create/Modify

### New Files to Create:
1. ✅ `Dockerfile` - Multi-stage Docker build
2. ✅ `docker-entrypoint.sh` - Container startup script (make executable)
3. ✅ `.dockerignore` - Exclude unnecessary files from build
4. ✅ `docker-compose.yml` - Example compose configuration
5. ✅ `.env.production.example` - Production environment template
6. ✅ `packages/frontend/.env.production` - Frontend production env

### Files to Modify:
1. ⚠️ `packages/frontend/vite.config.ts` - Update host to `0.0.0.0`
2. ✅ `packages/backend/src/server.ts` - Already correct (verify HOST=0.0.0.0)

### Commands to Run After Creating Files:
```bash
# Make entrypoint executable
chmod +x docker-entrypoint.sh

# Build the image (THIS IS THE MAIN GOAL)
docker build -t znakovni:latest .

# Verify build success
docker images | grep znakovni

# OPTIONAL: Test run locally
docker-compose up -d

# OPTIONAL: Check logs
docker-compose logs -f
```

---

## Important Implementation Notes for AI Agents

### 1. Dockerfile Multi-Stage Build Pattern:
- **Stage 1 (frontend-builder)**: Build React app with Vite
- **Stage 2 (backend-builder)**: Build Express app with TypeScript + Prisma
- **Stage 3 (production)**: Combine built artifacts, minimal runtime

### 2. Critical Prisma Considerations:
- Prisma client MUST be generated during build (`npx prisma generate`)
- Copy `node_modules/.prisma` directory to production stage
- Copy `prisma/` directory for migration files
- Run `prisma migrate deploy` in entrypoint script (NOT `migrate dev`)

### 3. Frontend Build Environment:
- Create `packages/frontend/.env.production` with `VITE_API_URL=http://localhost:3000`
- This ensures frontend makes API calls to localhost (same container)
- Vite will embed this at build time

### 4. Networking Inside Container:
- Backend binds to `0.0.0.0:3000` (accessible from frontend)
- Frontend served on `0.0.0.0:5173` (exposed to host)
- Both processes run in same container, communicate via localhost

### 5. Database Connection:
- MySQL server is EXTERNAL (not in container)
- Container must be able to reach MySQL IP (192.168.1.74)
- Ensure MySQL allows connections from Docker network
- Test with: `mysql -h 192.168.1.74 -u user -p znakovni`

### 6. Volume Mounting for Uploads:
- Backend uploads to `/app/packages/backend/uploads`
- Mount host directory to persist files across container restarts
- Example: `-v /mnt/user/appdata/znakovni/uploads:/app/packages/backend/uploads`

### 7. Environment Variables Priority:
**Required**:
- `DATABASE_URL` - MySQL connection string
- `SESSION_SECRET` - Random string (min 32 chars)

**Recommended**:
- `NODE_ENV=production`
- `PORT=3000`
- `HOST=0.0.0.0`
- `FRONTEND_URL=http://localhost:5173`

**Optional**:
- OAuth credentials (if using Google/Microsoft login)
- `RUN_SEED=true` (to seed database on first run)

### 8. Startup Sequence:
1. Container starts → runs `docker-entrypoint.sh`
2. Validate environment variables
3. Run Prisma migrations (`migrate deploy`)
4. Start backend in background
5. Wait 5 seconds for backend to initialize
6. Start frontend server (blocking, keeps container alive)
7. Trap signals for graceful shutdown

### 9. Build Success Checklist:
- [ ] All files created (Tasks 1-8)
- [ ] `docker build` command runs without errors
- [ ] Image `znakovni:latest` appears in `docker images` output
- [ ] No TypeScript compilation errors
- [ ] No Prisma generation errors
- [ ] Multi-stage build completes all 3 stages

### 10. Optional Testing Checklist (If Running Container):
- [ ] Container starts and stays running
- [ ] Backend health check responds: `curl http://localhost:5173/api/health`
- [ ] Frontend loads: `curl http://localhost:5173`
- [ ] Database connection works (check logs)
- [ ] Uploads persist after container restart

### 10. Deployment Flow (For User Reference):
```bash
# 1. Build image (AI AGENT TASK - MAIN GOAL)
docker build -t znakovni:latest .

# 2. OPTIONAL: Save image (if deploying to different machine)
docker save znakovni:latest -o znakovni.tar

# 3-7. USER HANDLES DEPLOYMENT
# - Transfer to Unraid
# - Load image
# - Create container
# - Configure Caddy
# - Monitor
```

---

## End of Guide

**GOAL**: Build Docker image `znakovni:latest` successfully.

**AI AGENT TASKS**:
1. Implement Tasks 1-8 (create/modify files)
2. Execute Task 9 (build image)
3. Verify image exists

**USER RESPONSIBILITY**: Deployment to Unraid and production configuration.

This guide provides complete instructions for dockerizing the Znakovni.hr application. Follow the tasks in order, and refer to the troubleshooting section if build issues arise.


