Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
87 changes: 87 additions & 0 deletions DOCKER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Docker Setup for AsyncReview

This guide explains how to run AsyncReview using Docker, both for production (immutable usage) and local development.

## Prerequisites

- **Docker** and **Docker Compose** installed on your machine.
- A `.env` file with your API keys (see [README.md](README.md) or copy `.env.example`).

**Note:** You *must* create a `.env` file before running any Docker commands.
```bash
cp .env.example .env
# Edit .env and add your GEMINI_API_KEY
```

## Quick Start (Production/User Mode)

To run the application in a stable, immutable container environment using the convenience Makefile command:

```bash
make docker-up
```

Alternatively, using Docker Compose directly:
```bash
docker compose up --build
```

- **Web UI:** http://localhost:3000
- **API:** Proxied via the Web UI at http://localhost:3000/api
- Note: The backend service is not directly exposed to the host in production mode.

To stop the application:
```bash
docker compose down
```

## Local Development

To run the application in development mode with hot-reloading (changes to your code are immediately reflected):

```bash
make docker-dev
```

Alternatively, using Docker Compose directly (requires both files):
```bash
docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build
```

- **Web UI:** http://localhost:5173 (Vite dev server)
- **API:** http://localhost:8000 (Direct access for debugging)

*Note: In development mode, the `web/` directory and the root directory are mounted into the containers. Changes you make locally will trigger reloads.*

## Running CLI Commands

You can run the `cr` CLI tool inside the backend container.

**In Production Mode:**
```bash
docker compose run --rm backend cr --help
docker compose run --rm backend cr review --url https://github.com/org/repo/pull/123
```

**In Development Mode:**
```bash
docker compose -f docker-compose.yml -f docker-compose.dev.yml run --rm backend cr --help
```

## Troubleshooting

### Port Conflicts
If port 3000, 8000, or 5173 are in use, you can modify the ports in `docker-compose.yml` or `docker-compose.dev.yml`.

### Deno/Pyodide Issues
The backend container installs Deno and caches the Pyodide environment during the build. If you encounter issues, try rebuilding:
```bash
docker compose build --no-cache backend
```

### Dependencies
If you add new Python dependencies to `pyproject.toml`, you must rebuild the backend container:
```bash
docker compose build backend
```
In development mode, since the source is mounted but dependencies are installed in the system python path, a rebuild is usually required to install new dependencies.
38 changes: 38 additions & 0 deletions Dockerfile.backend
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Base image
FROM python:3.11-slim

# Install system dependencies
RUN apt-get update && apt-get install -y \
curl \
unzip \
&& rm -rf /var/lib/apt/lists/*

# Install uv
# explicit path to ensure it's available
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

For reproducible builds, it's a best practice to pin dependencies to a specific version instead of using the :latest tag. This prevents unexpected build failures if a breaking change is introduced in a new version of the tool.

COPY --from=ghcr.io/astral-sh/uv:0.2.3 /uv /bin/uv


# Install Deno
ENV DENO_INSTALL="/root/.deno"
RUN curl -fsSL https://deno.land/install.sh | sh
Comment thread
ngoyal16 marked this conversation as resolved.
Outdated
ENV PATH="$DENO_INSTALL/bin:$PATH"

# Set working directory
WORKDIR /app

# Copy project files
COPY pyproject.toml README.md ./
COPY cr/ ./cr/
COPY cli/ ./cli/

# Install python dependencies
# We use --system to install into the container's system python environment
RUN uv pip install --system .
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

To improve Docker build performance and leverage layer caching, it's recommended to copy dependency files and install dependencies in a separate layer before copying the application source code. The current setup copies all source code before installing dependencies, which invalidates the cache on any file change, forcing a re-installation of all dependencies.

A better structure would be:

# Copy dependency manifest
COPY pyproject.toml ./

# Install dependencies
RUN uv pip install ... # Command to install from pyproject.toml

# Copy source code
COPY . .

The uv pip install --system . command complicates this pattern as it requires the source code. Consider exploring ways to install dependencies from pyproject.toml without the full source code present, for example by generating a requirements.txt during your build. This will significantly speed up rebuilds.


# Cache Deno dependencies
RUN deno cache npm:pyodide/pyodide.js

# Expose port
EXPOSE 8000

# Default command
CMD ["uvicorn", "cr.server:app", "--host", "0.0.0.0", "--port", "8000"]
Comment on lines +61 to +62
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-medium medium

The backend container runs as the root user by default. This violates the principle of least privilege and increases the security risk; if the application is compromised, the attacker would have full root access within the container. It is recommended to create a non-root user and switch to it using the USER instruction.

RUN useradd -m -u 1000 appuser
USER appuser

# Default command
CMD ["uvicorn", "cr.server:app", "--host", "0.0.0.0", "--port", "8000"]

25 changes: 23 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@
# make all - Build, install, and test
# make clean - Clean build artifacts
#
# Docker Usage:
# make docker-up - Run production environment (User mode)
# make docker-dev - Run development environment (Hot-reload)
#
# Version Management:
# make bump-patch - Bump patch version (0.5.0 -> 0.5.1)
# make bump-minor - Bump minor version (0.5.0 -> 0.6.0)
# make bump-major - Bump major version (0.5.0 -> 1.0.0)

.PHONY: all build install test publish clean bump-patch bump-minor bump-major version
.PHONY: all build install test publish clean bump-patch bump-minor bump-major version docker-up docker-dev

# Version file - single source of truth
VERSION_FILE := VERSION
Expand Down Expand Up @@ -75,6 +79,20 @@ clean:
@rm -rf .runtime_stage
@echo "Done."

# ============================================================
# Docker Helpers
# ============================================================

docker-up:
@if [ ! -f .env ]; then echo "Error: .env file not found. Copy .env.example to .env and fill in API keys."; exit 1; fi
@echo "==> Starting AsyncReview in Production Mode (Immutable)..."
@docker compose up --build

docker-dev:
@if [ ! -f .env ]; then echo "Error: .env file not found. Copy .env.example to .env and fill in API keys."; exit 1; fi
@echo "==> Starting AsyncReview in Development Mode (Hot-Reload)..."
@docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build

# ============================================================
# Version Management
# ============================================================
Expand Down Expand Up @@ -171,6 +189,10 @@ publish-npm: $(VERSION_FILE)
help:
@echo "AsyncReview Runtime Build System"
@echo ""
@echo "Docker Commands (Preferred):"
@echo " make docker-up - Run production environment (User mode)"
@echo " make docker-dev - Run development environment (Hot-reload)"
@echo ""
@echo "Build Commands:"
@echo " make build - Build runtime v$(VERSION) for $(PLATFORM)"
@echo " make install - Install built runtime locally"
Expand All @@ -197,4 +219,3 @@ help:
@echo "Dev Commands:"
@echo " make build-npx - Build TypeScript only"
@echo " make dev URL=... Q=... - Run dev mode"

2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ If you prefer manual configuration, point your agent to the skill definition fil

To run the full backend server or web interface locally, please see the [Installation Guide](INSTALLATION.md).

To run using **Docker**, see the [Docker Guide](DOCKER.md).

## License

MIT
24 changes: 24 additions & 0 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
services:
backend:
volumes:
- ./:/app
# Use anonymous volumes to prevent host directories from overwriting container directories
- /app/.venv
- /app/.deno
command: uvicorn cr.server:app --reload --host 0.0.0.0 --port 8000
ports:
- "8000:8000"
environment:
- WATCHFILES_FORCE_POLLING=true

frontend:
build:
target: dev
volumes:
- ./web:/app
- /app/node_modules
ports:
- "5173:5173"
environment:
- API_URL=http://backend:8000
command: npm run dev -- --host
21 changes: 21 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
services:
backend:
build:
context: .
dockerfile: Dockerfile.backend
env_file:
- .env
environment:
- PYTHONUNBUFFERED=1
restart: unless-stopped
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To make your service orchestration more robust, consider adding a healthcheck to the backend service. This ensures that dependent services, like the frontend, don't start until the backend is actually healthy and ready to accept traffic. Your application already exposes a /health endpoint that is perfect for this.

    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s


frontend:
build:
context: ./web
dockerfile: Dockerfile
target: prod
ports:
- "3000:80"
Comment thread
ngoyal16 marked this conversation as resolved.
Outdated
depends_on:
- backend
Comment on lines +19 to +20
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To complement the backend healthcheck, you should update depends_on to wait for the backend service to be healthy before starting the frontend. This prevents race conditions on startup.

    depends_on:
      backend:
        condition: service_healthy

restart: unless-stopped
25 changes: 25 additions & 0 deletions web/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Base stage
FROM node:20-alpine AS base
WORKDIR /app
COPY package*.json ./
RUN npm ci

# Development stage
FROM base AS dev
# Expose default Vite port
EXPOSE 5173
# Command to run dev server, binding to all interfaces
CMD ["npm", "run", "dev", "--", "--host"]

# Build stage
FROM base AS build
COPY . .
RUN npm run build

# Production stage
FROM nginx:alpine AS prod
COPY --from=build /app/dist /usr/share/nginx/html
# We will copy a custom nginx config to handle SPA routing and API proxying
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Comment on lines +20 to +49
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-medium medium

The production Nginx container runs as root to bind to port 80. For improved security, it is recommended to use a non-root Nginx image (like nginxinc/nginx-unprivileged) or configure Nginx to run as a non-root user and bind to a higher port (e.g., 8080).

40 changes: 40 additions & 0 deletions web/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
server {
listen 80;
server_name localhost;

location / {
root /usr/share/nginx/html;
index index.html index.htm;
# Support SPA routing (redirect to index.html for non-existent files)
try_files $uri $uri/ /index.html;
}

# Proxy API requests to the backend service
location /api/ {
proxy_pass http://backend:8000/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

# Support SSE (Server Sent Events)
proxy_buffering off;
proxy_cache off;
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding off;
}

# Health check
location /health {
proxy_pass http://backend:8000/health;
}

# Docs
location /docs {
proxy_pass http://backend:8000/docs;
}
location /openapi.json {
proxy_pass http://backend:8000/openapi.json;
}
}
Loading