Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/workflows/docker-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Docker Image CI

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
build-and-push:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Log in to Docker Hub
if: github.event_name == 'push'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
target: builder
push: ${{ github.event_name == 'push' }}
tags: callofcode07/callofcode:latest
# change this if using this for production
build-args: |
API_BASE_URL=http://coc-api:3000
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ yarn-error.log*
# local env files
.env*.local
.env

# docker local env files (keep *.example tracked)
docker/.env.local.*
!docker/.env.local.*.example
# vercel
.vercel

Expand Down
207 changes: 207 additions & 0 deletions DOCKER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
# Docker — Local Development Guide

This document explains how to spin up the full **Call of Code** local development environment using Docker Compose.

The stack consists of two services running on a shared Docker network (`coc-local`):

| Service | Image / Source | Port |
| ----------- | --------------------------------------- | ------ |
| `coc-api` | `callofcode07/coc-api:latest` (Docker Hub) | 3000 |
| `frontend` | Built locally from this repo (`Dockerfile`) | 3001 |

---

## Prerequisites

- [Docker Desktop](https://www.docker.com/products/docker-desktop/) ≥ 24 **or** Docker Engine + Docker Compose plugin ≥ 2.22
- Git

---

## Quick Start

### 1. Configure environment variables

The Compose setup reads env files from the `docker/` directory. Copy the example files and fill in your values:

```bash
# COC API service
cp docker/.env.local.coc-api.example docker/.env.local.coc-api

# Frontend service
cp docker/.env.local.frontend.example docker/.env.local.frontend
```

> **Never commit** the real `docker/.env.local.*` files — they are already listed in `.gitignore`.

### 2. Start the services

```bash
docker compose up --build
```

| Flag | Effect |
| ----------- | ------------------------------------------------- |
| `--build` | (Re)build the frontend image before starting |
| `--watch` | Enable hot-reload — see [Hot Reload](#hot-reload) |
| `-d` | Run in the background (detached mode) |

The frontend will be available at **http://localhost:3001** once the `coc-api` health check passes.

---

## Environment Variables

### `docker/.env.local.coc-api`

Consumed by the `coc-api` container. Credentials for the Supabase / Postgres backend.

| Variable | Description |
| ------------------------- | --------------------------------------------------------- |
| `DATABASE_URL` | Postgres pooler connection string (used at runtime) |
| `DIRECT_URL` | Postgres direct connection string (used for migrations) |
| `SUPABASE_URL` | Your Supabase project URL |
| `SUPABASE_SERVICE_ROLE_KEY` | Supabase service-role JWT (keep this secret!) |
| `NODE_ENV` | Set to `development` for local use |

Example:
```dotenv
DATABASE_URL=postgresql://postgres.<ref>:<password>@aws-0-ap-south-1.pooler.supabase.com:5432/postgres
DIRECT_URL=postgresql://postgres.<ref>:<password>@aws-0-ap-south-1.pooler.supabase.com:5432/postgres
SUPABASE_URL=https://<ref>.supabase.co
SUPABASE_SERVICE_ROLE_KEY=<your-service-role-key>
NODE_ENV=development
```

---

### `docker/.env.local.frontend`

Consumed by the `frontend` container at runtime.

| Variable | Description |
| -------------- | --------------------------------------------------- |
| `API_BASE_URL` | URL the frontend uses to reach the API. Within the Docker network this is `http://coc-api:3000` |
| `GITHUB_TOKEN` | GitHub personal access token (optional, for contribution graphs) |

Example:
```dotenv
API_BASE_URL=http://coc-api:3000
GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx
```

---

## Hot Reload

The Compose file uses Docker's `develop.watch` feature to sync source files into the running container **without a full rebuild**.

Start with watch mode enabled:

```bash
docker compose up --watch
```

Synced paths:

| Local path | Container path | Action |
| -------------- | ----------------- | ------- |
| `./app` | `/app/app` | `sync` |
| `./components` | `/app/components` | `sync` |
| `./lib` | `/app/lib` | `sync` |
| `./public` | `/app/public` | `sync` |
| `package.json` / `package-lock.json` | — | `rebuild` (triggers a full image rebuild) |

> **Note:** `sync` changes are reflected instantly. Dependency changes (`package.json`) trigger a full rebuild automatically.

---

## Dockerfile Stages

The multi-stage `Dockerfile` has three stages:

| Stage | Base Image | Purpose |
| --------- | ----------------- | ------------------------------------------------ |
| `deps` | `node:20-alpine` | Install `node_modules` with `npm ci` |
| `builder` | `node:20-alpine` | Copy deps + source, run `npm run build`. **Used by Compose in dev** (keeps dev deps intact). |
| `runner` | `node:20-alpine` | Lean production image — only production artefacts |

The Compose file targets the `builder` stage so that dev dependencies (like TypeScript types) remain available inside the container.

---

## Useful Commands

```bash
# Start all services (foreground)
docker compose up --build

# Start with hot-reload
docker compose up --build --watch

# Start in background
docker compose up -d --build

# View logs for a specific service
docker compose logs -f frontend
docker compose logs -f coc-api

# Stop all services
docker compose down

# Stop and remove volumes
docker compose down -v

# Rebuild only the frontend image
docker compose build frontend

# Open a shell inside the frontend container
docker compose exec frontend sh

# Check service health
docker compose ps
```

---

## Service Health Check

The `coc-api` container exposes a health endpoint at `GET /health`. Docker polls it every **15 seconds** (3 retries, 5 s timeout, 15 s start period). The `frontend` service will not start until `coc-api` is reported **healthy**.

```yaml
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
interval: 15s
timeout: 5s
retries: 3
start_period: 15s
```

---

## Troubleshooting

### Frontend can't reach the API

- Confirm `docker/.env.local.frontend` has `API_BASE_URL=http://coc-api:3000`.
- Check that `coc-api` is healthy: `docker compose ps`.
- Inspect API logs: `docker compose logs coc-api`.

### Port already in use

Change the host-side port mapping in `docker-compose.yml`:
```yaml
ports:
- "3002:3001" # map host 3002 → container 3001
```

### Hot-reload not working

Ensure you started with `--watch`: `docker compose up --watch`. The feature requires Docker Compose ≥ 2.22.

### Pulling a fresh copy of the API image

```bash
docker compose pull coc-api
docker compose up --build
```
52 changes: 52 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Stage 1 – deps: install node_modules with npm ci for reproducibility
FROM node:20-alpine AS deps
WORKDIR /app

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


# Stage 2 – builder: compile the Next.js application
FROM node:20-alpine AS builder
WORKDIR /app

COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Build-time env vars (non-secret, baked into the bundle)
ARG API_BASE_URL
ENV API_BASE_URL=$API_BASE_URL

RUN npm run build

# Stage 3 – runner: lean production image
FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production

# Create a non-root group and user to run the application securely
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 --ingroup nodejs nextjs

# Next.js standalone output (enable in next.config if needed)
# COPY --from=builder /app/.next/standalone ./
# COPY --from=builder /app/.next/static ./.next/static
# COPY --from=builder /app/public ./public

# Standard (non-standalone) output
# node_modules sourced from deps stage; .next artefacts from builder
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next ./.next
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json

# Transfer ownership of the working directory to the non-root user
RUN chown -R nextjs:nodejs /app

# Drop privileges — never run production containers as root
USER nextjs

EXPOSE 3001

CMD ["npm", "run", "start"]
Loading
Loading