> ## Documentation Index
> Fetch the complete documentation index at: https://pv-pushkarverma-skillrise-21.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Docker Setup

> Run SkillRise with Docker and Docker Compose for a containerized development and production environment

## Overview

SkillRise includes Docker configurations for both development and production deployments. Docker provides a consistent, isolated environment that works across all platforms.

> [!NOTE]
>
> **Benefits of Docker:**
>
> * No need to install Node.js, MongoDB, or other dependencies locally
> * Consistent environment across development and production
> * Easy scaling and deployment
> * Pre-configured networking between services

***

## Prerequisites

> **Docker**
>
> Docker Engine 20.x or higher

> **Docker Compose**
>
> v2.x (included with Docker Desktop)

### Install Docker

**macOS**

**Option 1: Docker Desktop (Recommended)**

1. Download [Docker Desktop for Mac](https://www.docker.com/products/docker-desktop/)
2. Install the `.dmg` file
3. Open Docker Desktop and follow the setup wizard
4. Verify installation:

```bash theme={null}
docker --version
# Expected: Docker version 24.x.x

docker compose version
# Expected: Docker Compose version v2.x.x
```

**Option 2: Homebrew**

```bash theme={null}
brew install --cask docker
```

**Linux**

```bash theme={null}
# Remove old versions
sudo apt-get remove docker docker-engine docker.io containerd runc

# Install dependencies
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg lsb-release

# Add Docker's official GPG key
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

# Set up repository
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Install Docker Engine
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin

# Add your user to docker group (to avoid sudo)
sudo usermod -aG docker $USER
newgrp docker

# Verify
docker --version
docker compose version
```

**Windows**

**Docker Desktop for Windows (Recommended)**

1. Download [Docker Desktop for Windows](https://www.docker.com/products/docker-desktop/)
2. Run the installer (requires WSL 2)
3. Restart your computer
4. Open Docker Desktop
5. Verify in PowerShell:

```powershell theme={null}
docker --version
docker compose version
```

> [!NOTE]
>
> Requires Windows 10/11 Pro, Enterprise, or Education with Hyper-V support.

***

## Project Docker Structure

SkillRise includes the following Docker configuration files:

```
skillrise/
├── docker-compose.yml          # Production compose file
├── server/
│   └── Dockerfile              # Backend container definition
└── client/
    ├── Dockerfile              # Frontend container definition (multi-stage)
    └── nginx.conf              # Nginx configuration for serving React app
```

***

## Dockerfile Overview

### Backend Dockerfile

The server uses a simple Node.js Alpine image for a lightweight container.

```dockerfile server/Dockerfile theme={null}
FROM node:20-alpine

WORKDIR /app

# Create non-root user and group early
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# Install dependencies
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

# Copy source code with correct ownership
COPY --chown=appuser:appgroup . .

# Switch to non-root user
USER appuser

EXPOSE 3000
CMD ["node", "server.js"]
```

> **Key features**
>
> * **Alpine Linux** - Minimal base image (\~5 MB vs 1+ GB for full Ubuntu)
> * **Non-root user** - Security best practice (runs as `appuser` instead of `root`)
> * **Production dependencies only** - `npm ci --omit=dev` skips devDependencies
> * **Layer caching** - `package.json` copied before source code for faster rebuilds

### Frontend Dockerfile

The client uses a **multi-stage build** to keep the final image small.

```dockerfile client/Dockerfile theme={null}
# Stage 1: Build
FROM node:20-alpine AS builder

WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci

ARG VITE_CLERK_PUBLISHABLE_KEY
ARG VITE_RAZORPAY_KEY_ID
ARG VITE_BACKEND_URL

ENV VITE_CLERK_PUBLISHABLE_KEY=$VITE_CLERK_PUBLISHABLE_KEY
ENV VITE_RAZORPAY_KEY_ID=$VITE_RAZORPAY_KEY_ID
ENV VITE_BACKEND_URL=$VITE_BACKEND_URL

COPY . .
RUN npm run build

# Stage 2: Serve with nginx
FROM nginx:alpine

COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```

> **Key features**
>
> * **Multi-stage build** - Build stage is discarded, final image only contains static files + nginx
> * **Build arguments** - Environment variables are baked into the build at compile time
> * **Nginx for serving** - Efficient static file server with SPA fallback routing
> * **Tiny image size** - Final image is \~50 MB (vs \~1 GB if we kept Node.js)

### Nginx Configuration

```nginx client/nginx.conf theme={null}
server {
    listen 80;
    root /usr/share/nginx/html;
    index index.html;

    # SPA fallback — all routes serve index.html
    location / {
        try_files $uri $uri/ /index.html;
    }
}
```

> [!NOTE]
>
> The `try_files` directive ensures that React Router routes (e.g., `/courses/123`) serve `index.html` instead of returning 404.

***

## Docker Compose Configuration

The `docker-compose.yml` file orchestrates both services:

```yaml docker-compose.yml theme={null}
services:
  server:
    image: pushkarverma/skillrise-server:latest
    ports:
      - '3000:3000'
    env_file:
      - ./server/.env
    restart: unless-stopped

  client:
    image: pushkarverma/skillrise-client:latest
    ports:
      - '80:80'
    depends_on:
      - server
    restart: unless-stopped
```

**services.server.image** `string`

Pre-built Docker Hub image for the backend. You can replace this with `build: ./server` to build locally.

**services.server.ports** `array`

Maps host port 3000 to container port 3000 (Express server).

**services.server.env\_file** `string`

Loads environment variables from `server/.env` into the container.

**services.client.depends\_on** `array`

Ensures the server starts before the client container.

***

## Running with Docker Compose

### Using Pre-built Images (Quickest)

The easiest way is to use pre-built images from Docker Hub:

**1. Create environment files**

Ensure both `server/.env` and `client/.env` are configured. See [Configuration](https://pv-pushkarverma-skillrise-21.mintlify.site/getting-started/configuration.md).

**2. Start services**

```bash theme={null}
docker compose up
```

> **Expected output**
>
> ```
> [+] Running 2/2
>  ⠿ Container skillrise-server-1  Started  1.2s
>  ⠿ Container skillrise-client-1  Started  2.3s
> Attaching to skillrise-client-1, skillrise-server-1
> skillrise-server-1  | Database Connected
> skillrise-server-1  | Server running on port 3000
> skillrise-client-1  | /docker-entrypoint.sh: Configuration complete; ready for start up
> ```

> [!NOTE]
>
> The `-d` flag runs in detached mode (background):
>
> ```bash theme={null}
> docker compose up -d
> ```

**3. Access the application**

* **Frontend:** [http://localhost](http://localhost)
* **Backend API:** [http://localhost:3000](http://localhost:3000)

**4. Stop services**

```bash theme={null}
# Stop containers (keep data)
docker compose stop

# Stop and remove containers
docker compose down
```

### Building Locally

To build images from source instead of using Docker Hub:

**1. Modify docker-compose.yml**

Replace image references with build contexts:

```yaml docker-compose.yml theme={null}
services:
  server:
    build: ./server
    ports:
      - '3000:3000'
    env_file:
      - ./server/.env
    restart: unless-stopped

  client:
    build:
      context: ./client
      args:
        VITE_CLERK_PUBLISHABLE_KEY: ${VITE_CLERK_PUBLISHABLE_KEY}
        VITE_RAZORPAY_KEY_ID: ${VITE_RAZORPAY_KEY_ID}
        VITE_BACKEND_URL: ${VITE_BACKEND_URL}
    ports:
      - '80:80'
    depends_on:
      - server
    restart: unless-stopped
```

**2. Create root .env file**

For client build args, create a `.env` in the project root:

```env .env theme={null}
VITE_CLERK_PUBLISHABLE_KEY=pk_test_...
VITE_RAZORPAY_KEY_ID=rzp_test_...
VITE_BACKEND_URL=http://localhost:3000
```

**3. Build and run**

```bash theme={null}
docker compose up --build
```

This builds both images from scratch (takes 2-5 minutes on first run).

> **Build output**
>
> ```
> [+] Building 142.3s (23/23) FINISHED
>  => [server internal] load build definition from Dockerfile  0.1s
>  => [server] COPY package.json package-lock.json ./          0.2s
>  => [server] RUN npm ci --omit=dev                           45.3s
>  => [client builder] RUN npm run build                       89.7s
>  => [client] COPY --from=builder /app/dist                   0.3s
> [+] Running 2/2
>  ⠿ Container skillrise-server-1  Started  1.1s
>  ⠿ Container skillrise-client-1  Started  2.2s
> ```

***

## Docker Commands Reference

```bash Start Services theme={null}
# Start in foreground (see logs)
docker compose up

# Start in background
docker compose up -d

# Rebuild images and start
docker compose up --build

# Start specific service
docker compose up server
```

```bash Stop Services theme={null}
# Stop (containers remain)
docker compose stop

# Stop and remove containers
docker compose down

# Remove containers and volumes
docker compose down -v
```

```bash View Logs theme={null}
# All services
docker compose logs

# Follow logs (real-time)
docker compose logs -f

# Specific service
docker compose logs -f server

# Last 100 lines
docker compose logs --tail=100
```

```bash Execute Commands theme={null}
# Run seed script inside server container
docker compose exec server node seed.js

# Open shell in server container
docker compose exec server sh

# Open shell in client container
docker compose exec client sh

# Run npm command
docker compose exec server npm run lint
```

```bash Inspect Services theme={null}
# List running containers
docker compose ps

# View service details
docker compose config

# Check resource usage
docker stats
```

```bash Cleanup theme={null}
# Remove stopped containers
docker compose rm

# Remove all images
docker compose down --rmi all

# Remove unused Docker resources
docker system prune -a
```

***

## Seeding Data in Docker

To populate the database with demo data:

```bash theme={null}
# Start services
docker compose up -d

# Run seed script inside server container
docker compose exec server node seed.js
```

> **Expected output**
>
> ```
> 🧹 Clearing existing seed data...
> 👤 Seeding users...
>    ✓ 10 users
> 📚 Seeding courses...
>    ✓ 10 courses
> 💳 Seeding purchases...
>    ✓ 19 purchases
> 📈 Seeding course progress...
>    ✓ 19 progress records
> 🧠 Seeding quizzes...
>    ✓ 15 quizzes
> 🌐 Seeding community groups...
>    ✓ 4 groups
> 💬 Seeding posts...
>    ✓ 12 posts
> 💬 Seeding replies...
>    ✓ 18 replies
> ✅ Database seeded successfully!
> ```

See [Seeding Data](https://pv-pushkarverma-skillrise-21.mintlify.site/getting-started/seeding-data.md) for more details.

***

## Environment Variables in Docker

### Backend

Environment variables are loaded from `server/.env` via the `env_file` directive:

```yaml theme={null}
services:
  server:
    env_file:
      - ./server/.env
```

All variables in `server/.env` are automatically available in the container.

### Frontend (Build Arguments)

For the client, environment variables must be passed as **build arguments** because Vite bundles them at build time:

```yaml theme={null}
services:
  client:
    build:
      context: ./client
      args:
        VITE_CLERK_PUBLISHABLE_KEY: ${VITE_CLERK_PUBLISHABLE_KEY}
        VITE_BACKEND_URL: ${VITE_BACKEND_URL}
```

These values are read from a root `.env` file (not `client/.env`).

> [!WARNING]
>
> **Important:** Changes to `VITE_*` variables require rebuilding the client image:
>
> ```bash theme={null}
> docker compose up --build client
> ```

***

## Production Deployment

For production, use the pre-built images with a Docker registry:

### Building and Pushing Images

**1. Build production images**

```bash theme={null}
# Build server
docker build -t your-registry/skillrise-server:latest ./server

# Build client (with production env vars)
docker build \
  --build-arg VITE_CLERK_PUBLISHABLE_KEY=pk_live_... \
  --build-arg VITE_BACKEND_URL=https://api.yourapp.com \
  -t your-registry/skillrise-client:latest \
  ./client
```

**2. Push to registry**

```bash theme={null}
docker push your-registry/skillrise-server:latest
docker push your-registry/skillrise-client:latest
```

**3. Update docker-compose.yml on server**

```yaml theme={null}
services:
  server:
    image: your-registry/skillrise-server:latest
    # ... rest of config

  client:
    image: your-registry/skillrise-client:latest
    # ... rest of config
```

**4. Deploy**

```bash theme={null}
docker compose pull
docker compose up -d
```

> [!NOTE]
>
> The official SkillRise images are available at:
>
> * `pushkarverma/skillrise-server:latest`
> * `pushkarverma/skillrise-client:latest`

***

## Docker vs Local Development

| Feature | Local Development | Docker |
| - | - | - |
| **Setup Time** | 15-30 minutes | 5 minutes |
| **Dependencies** | Node.js, MongoDB, npm | Docker only |
| **Hot Reload** | ✅ Built-in (Vite, nodemon) | ❌ Requires volume mounts |
| **Environment Parity** | May differ | ✅ Identical to production |
| **Resource Usage** | Low | Medium (containers overhead) |
| **Debugging** | ✅ Easy (direct access) | ⚠️ Requires `docker exec` |
| **Best For** | Active development | Testing, CI/CD, production |

> [!NOTE]
>
> **Recommendation:** Use **local development** for day-to-day coding (faster hot reload). Use **Docker** for testing full-stack integration and deployment.

***

## Troubleshooting

> **Port already in use**
>
> **Error:** `Bind for 0.0.0.0:3000 failed: port is already allocated`
>
> **Solutions:**
>
> 1. Stop the process using the port:
>    ```bash theme={null}
>    # Find process
>    lsof -ti:3000 | xargs kill -9
>    ```
> 2. Or change the port in `docker-compose.yml`:
>    ```yaml theme={null}
>    services:
>      server:
>        ports:
>          - '4000:3000'  # Host:Container
>    ```

> **Build fails with npm install errors**
>
> **Error:** `npm ERR! network` or `npm ERR! code ENOTFOUND`
>
> **Solutions:**
>
> 1. Check your internet connection
> 2. Clear Docker build cache:
>    ```bash theme={null}
>    docker builder prune -a
>    docker compose build --no-cache
>    ```
> 3. Use a different npm registry:
>    ```dockerfile theme={null}
>    RUN npm config set registry https://registry.npmjs.org/
>    RUN npm ci
>    ```

> **Environment variables not loading**
>
> **Symptom:** App can't connect to database or external APIs
>
> **Solutions:**
>
> 1. Verify `server/.env` exists and has correct values
> 2. Restart containers:
>    ```bash theme={null}
>    docker compose down
>    docker compose up
>    ```
> 3. For client env vars, ensure they're in root `.env` and rebuild:
>    ```bash theme={null}
>    docker compose up --build client
>    ```

> **Client shows blank page or 404**
>
> **Symptom:** Frontend loads but shows white screen or errors
>
> **Solutions:**
>
> 1. Check browser console for errors (F12 → Console)
> 2. Verify `VITE_BACKEND_URL` matches the server URL:
>    ```env theme={null}
>    VITE_BACKEND_URL=http://localhost:3000
>    ```
> 3. Rebuild client image:
>    ```bash theme={null}
>    docker compose up --build client
>    ```
> 4. Check nginx logs:
>    ```bash theme={null}
>    docker compose logs client
>    ```

> **Database connection failed in container**
>
> **Error:** `MongoServerError: connect ECONNREFUSED`
>
> **Solutions:**
>
> 1. If using local MongoDB, change `MONGODB_URI` to use host network:
>    ```env theme={null}
>    # macOS/Windows
>    MONGODB_URI=mongodb://host.docker.internal:27017
>
>    # Linux
>    MONGODB_URI=mongodb://172.17.0.1:27017
>    ```
> 2. Or add MongoDB as a service in `docker-compose.yml`:
>    ```yaml theme={null}
>    services:
>      mongo:
>        image: mongo:8
>        ports:
>          - '27017:27017'
>        volumes:
>          - mongo-data:/data/db
>
>      server:
>        # ...
>        environment:
>          - MONGODB_URI=mongodb://mongo:27017
>        depends_on:
>          - mongo
>
>    volumes:
>      mongo-data:
>    ```

> **Seed script fails inside container**
>
> **Error:** `MongoServerError: bad auth`
>
> **Solutions:**
>
> 1. Ensure server container is running:
>    ```bash theme={null}
>    docker compose ps
>    ```
> 2. Verify `MONGODB_URI` in `server/.env` is correct
> 3. Check server logs:
>    ```bash theme={null}
>    docker compose logs server
>    ```

***

## Next Steps

> **[Seeding Data](https://pv-pushkarverma-skillrise-21.mintlify.site/getting-started/seeding-data.md)**
>
> Populate database with demo data

> **[CI/CD Pipeline](https://pv-pushkarverma-skillrise-21.mintlify.site/deployment/ci-cd.md)**
>
> Automate builds with GitHub Actions

> **[Production Deploy](https://pv-pushkarverma-skillrise-21.mintlify.site/deployment/production)**
>
> Deploy to cloud providers


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.