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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the docker-image.yml workflow file to see the full context
cat -n .github/workflows/docker-image.yml

Repository: call-0f-code/call-of-code

Length of output: 1155


🏁 Script executed:

# Now examine docker-compose.yml to see what images it references
cat -n docker-compose.yml

Repository: call-0f-code/call-of-code

Length of output: 2145


🏁 Script executed:

# Search for references to the image names across the repository
rg "callofcode07/callofcode|callofcode07/coc-api|coc-frontend" --type yaml --type yml -n

Repository: call-0f-code/call-of-code

Length of output: 191


🏁 Script executed:

# Search more broadly for image name references
rg "callofcode07|coc-api|coc-frontend" -n --no-heading | head -30

Repository: call-0f-code/call-of-code

Length of output: 163


🏁 Script executed:

# Check the Dockerfile to see what's being built
cat -n Dockerfile | head -50

Repository: call-0f-code/call-of-code

Length of output: 1481


🏁 Script executed:

# Search for any README or documentation that might explain the image naming
find . -name "README*" -o -name "*.md" | xargs grep -l "callofcode07\|docker" 2>/dev/null | head -5

Repository: call-0f-code/call-of-code

Length of output: 187


🏁 Script executed:

# Check DOCKER.md for documentation about the image naming and architecture
cat -n DOCKER.md

Repository: call-0f-code/call-of-code

Length of output: 7972


🏁 Script executed:

# Also check README.md for relevant information
cat README.md | grep -A 20 -B 5 "docker\|image" -i | head -60

Repository: call-0f-code/call-of-code

Length of output: 1655


Published image name doesn't match what the dev stack uses; align or document the separation.

The workflow publishes to callofcode07/callofcode:latest, but the dev stack in docker-compose.yml pulls callofcode07/coc-api:latest for the API and builds the frontend locally as coc-frontend:dev. The published callofcode07/callofcode image is unused in the local development workflow, making this an orphaned build artifact. Per DOCKER.md, the stack has no reference to this image.

Additionally, publishing only :latest leaves no immutable tag for rollbacks or debugging past deployments.

Consider either:

  • Aligning the published image name with what dev stack consumes, or
  • Adding a documentation comment explaining why the published image differs from local builds
🔧 Suggested tagging
-          tags: callofcode07/callofcode:latest
+          tags: |
+            callofcode07/callofcode:latest
+            callofcode07/callofcode:${{ github.sha }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
tags: callofcode07/callofcode:latest
tags: |
callofcode07/callofcode:latest
callofcode07/callofcode:${{ github.sha }}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/docker-image.yml at line 32, In the docker-image.yml
workflow file, the tags property at line 32 currently specifies
`callofcode07/callofcode:latest`, which does not align with the image names
referenced in docker-compose.yml (callofcode07/coc-api:latest for the API
service). Either update the published image name in the tags property to match
what the dev stack expects (callofcode07/coc-api), or add a comment in the
workflow explaining the intentional separation between the published image and
local development images. Additionally, replace the single `:latest` tag with
multiple tags including both a version-based immutable tag (e.g., using git
commit SHA or version number) and the `:latest` tag to enable proper rollback
and debugging of past deployments.

# 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"]
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment on lines +22 to +52

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Runtime environment variable API_BASE_URL is not available in the runner stage.

The builder stage sets API_BASE_URL as an ARG and ENV (lines 17–18), but build-time ARGs are not inherited by subsequent stages. The runner stage has no corresponding ENV directive to make API_BASE_URL available at runtime.

The upstream code (app/api/projects-with-members/route.ts and app/achievements/page.tsx) requires process.env.API_BASE_URL at runtime. When the runner image is executed standalone (outside Compose), the app will fail with "API_BASE_URL not set" errors (HTTP 500).

Recommendation: Either:

  • Accept API_BASE_URL as a runtime ENV var in the runner stage (users provide it via docker run -e API_BASE_URL=... or in a .env file).
  • Or document that the runner image requires API_BASE_URL to be passed at container startup.
🔧 Proposed fix: Accept API_BASE_URL at runtime
 # Stage 3 – runner: lean production image
 FROM node:20-alpine AS runner
 WORKDIR /app
 
 ENV NODE_ENV=production
+ENV API_BASE_URL=""  # Set at runtime via -e or .env
 
 # Next.js standalone output (enable in next.config if needed)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Dockerfile` around lines 22 - 41, The runner stage in the Dockerfile does not
declare the API_BASE_URL environment variable, which is needed at runtime by the
Next.js application code in app/api/projects-with-members/route.ts and
app/achievements/page.tsx. After the ENV NODE_ENV=production line in the runner
stage (FROM node:20-alpine AS runner), add an ENV directive to declare
API_BASE_URL as an environment variable. This allows users to provide the value
at runtime via docker run -e API_BASE_URL=... or similar runtime mechanisms,
ensuring the variable is available when the container starts.

Loading
Loading