> ## Documentation Index
> Fetch the complete documentation index at: https://skillrisedocs.pushkarverma.online/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.

<Info>
  **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
</Info>

***

## Prerequisites

<CardGroup cols={2}>
  <Card title="Docker" icon="docker" iconType="duotone">
    Docker Engine 20.x or higher
  </Card>

  <Card title="Docker Compose" icon="layer-group" iconType="duotone">
    v2.x (included with Docker Desktop)
  </Card>
</CardGroup>

### Install Docker

<Tabs>
  <Tab title="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
    ```
  </Tab>

  <Tab title="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
    ```
  </Tab>

  <Tab title="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.
    </Note>
  </Tab>
</Tabs>

***

## 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"]
```

<Accordion title="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
</Accordion>

### 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;"]
```

<Accordion title="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)
</Accordion>

### 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.
</Note>

***

## 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
```

<ParamField path="services.server.image" type="string">
  Pre-built Docker Hub image for the backend. You can replace this with `build: ./server` to build locally.
</ParamField>

<ParamField path="services.server.ports" type="array">
  Maps host port 3000 to container port 3000 (Express server).
</ParamField>

<ParamField path="services.server.env_file" type="string">
  Loads environment variables from `server/.env` into the container.
</ParamField>

<ParamField path="services.client.depends_on" type="array">
  Ensures the server starts before the client container.
</ParamField>

***

## Running with Docker Compose

### Using Pre-built Images (Quickest)

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

<Steps>
  <Step title="Create environment files">
    Ensure both `server/.env` and `client/.env` are configured. See [Configuration](/getting-started/configuration).
  </Step>

  <Step title="Start services">
    ```bash theme={null}
    docker compose up
    ```

    <Accordion title="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
      ```
    </Accordion>

    <Note>
      The `-d` flag runs in detached mode (background):

      ```bash theme={null}
      docker compose up -d
      ```
    </Note>
  </Step>

  <Step title="Access the application">
    * **Frontend:** [http://localhost](http://localhost)
    * **Backend API:** [http://localhost:3000](http://localhost:3000)
  </Step>

  <Step title="Stop services">
    ```bash theme={null}
    # Stop containers (keep data)
    docker compose stop

    # Stop and remove containers
    docker compose down
    ```
  </Step>
</Steps>

### Building Locally

To build images from source instead of using Docker Hub:

<Steps>
  <Step title="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
    ```
  </Step>

  <Step title="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
    ```
  </Step>

  <Step title="Build and run">
    ```bash theme={null}
    docker compose up --build
    ```

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

    <Accordion title="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
      ```
    </Accordion>
  </Step>
</Steps>

***

## Docker Commands Reference

<CodeGroup>
  ```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
  ```
</CodeGroup>

***

## 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
```

<Accordion title="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!
  ```
</Accordion>

See [Seeding Data](/getting-started/seeding-data) 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
  ```
</Warning>

***

## Production Deployment

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

### Building and Pushing Images

<Steps>
  <Step title="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
    ```
  </Step>

  <Step title="Push to registry">
    ```bash theme={null}
    docker push your-registry/skillrise-server:latest
    docker push your-registry/skillrise-client:latest
    ```
  </Step>

  <Step title="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
    ```
  </Step>

  <Step title="Deploy">
    ```bash theme={null}
    docker compose pull
    docker compose up -d
    ```
  </Step>
</Steps>

<Note>
  The official SkillRise images are available at:

  * `pushkarverma/skillrise-server:latest`
  * `pushkarverma/skillrise-client:latest`
</Note>

***

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

<Info>
  **Recommendation:** Use **local development** for day-to-day coding (faster hot reload). Use **Docker** for testing full-stack integration and deployment.
</Info>

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="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
       ```
  </Accordion>

  <Accordion title="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
       ```
  </Accordion>

  <Accordion title="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
       ```
  </Accordion>

  <Accordion title="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
       ```
  </Accordion>

  <Accordion title="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:
       ```
  </Accordion>

  <Accordion title="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
       ```
  </Accordion>
</AccordionGroup>

***

## Next Steps

<CardGroup cols={3}>
  <Card title="Seeding Data" icon="database" href="/getting-started/seeding-data">
    Populate database with demo data
  </Card>

  <Card title="CI/CD Pipeline" icon="rocket" href="/deployment/ci-cd">
    Automate builds with GitHub Actions
  </Card>

  <Card title="Production Deploy" icon="server" href="/deployment/production">
    Deploy to cloud providers
  </Card>
</CardGroup>
