Skip to content
Open
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
32 changes: 32 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
__pycache__
*.pyc
*.pyo
*.pyd
.Python
env/
venv/
.venv/
pip-log.txt
pip-delete-this-directory.txt
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.log
.git
.mypy_cache
.pytest_cache
.hypothes
.DS_Store
.env
.env.*
!.env.example

# Web artifacts (not needed for backend build context, though we use specific COPY)
web/node_modules/
web/dist/
web/.next/
web/coverage/
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.
62 changes: 62 additions & 0 deletions Dockerfile.backend
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Base image
FROM python:3.11.8-slim-bookworm

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

# Create a non-root user
RUN groupadd -r appuser && useradd -r -g appuser appuser

# Install uv
COPY --from=ghcr.io/astral-sh/uv:0.5.29 /uv /bin/uv

# Install Deno
# We install Deno to a global location or user's home
ENV DENO_INSTALL="/usr/local"
ENV DENO_VERSION="v1.40.3"
# Install deno manually to /usr/local/bin so it's accessible to all users
RUN curl -fsSL https://deno.land/x/install/install.sh | sh -s ${DENO_VERSION}

# Set working directory
WORKDIR /app

# Copy dependency definition only to leverage cache
COPY pyproject.toml README.md ./

# Compile and install dependencies
# We install to system python which is fine in docker, but we need to ensure permissions if we were to install more later.
# However, system install requires root. We do this BEFORE switching user.
RUN uv pip compile pyproject.toml -o requirements.txt && \
uv pip install --system -r requirements.txt

# Copy source code
COPY cr/ ./cr/
COPY cli/ ./cli/

# Install the project itself
RUN uv pip install --system --no-deps .

# Cache Deno dependencies
# We need to run this as the user who will run the app, OR ensure the cache is accessible.
# If we run as appuser, Deno will cache to $HOME/.cache/deno
# So we should switch user first.

# Change ownership of the app directory
RUN chown -R appuser:appuser /app

# Switch to non-root user
USER appuser

# Cache Deno dependencies (will be stored in /home/appuser/.cache/deno or similar)
# We need to ensure appuser has a home directory or set DENO_DIR
ENV DENO_DIR="/app/.deno_cache"
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

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
28 changes: 28 additions & 0 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
services:
backend:
# Run as root in development to avoid permission issues with bind mounts
user: root
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:
# Run as root in development to ensure node_modules write access and hot reloading
user: root
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

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:8080"
depends_on:
- backend
Comment on lines +19 to +20

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
13 changes: 13 additions & 0 deletions web/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
node_modules
dist
.env
.env.*
!.env.example
.git
.DS_Store
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.idea
.vscode
coverage
49 changes: 49 additions & 0 deletions web/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# 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 build artifacts
COPY --from=build /app/dist /usr/share/nginx/html

# Copy custom nginx config
COPY nginx.conf /etc/nginx/conf.d/default.conf

# Configure permissions for non-root user
# Nginx alpine image uses 'nginx' user (uid 101)
# We need to ensure it can write to cache/pid directories if they are used,
# though with standard config it might write to /var/run which is root owned.
# More commonly we just need to ensure it can read the static files (which it can)
# and bind to the port (which is now 8080).

# Update nginx.conf to use /tmp for pid/cache if strictly necessary,
# but usually just changing the port is enough for the main process
# if we don't need to write to system paths.
# However, standard nginx image tries to write to /var/cache/nginx and /var/run/nginx.pid
RUN chown -R nginx:nginx /var/cache/nginx && \
chown -R nginx:nginx /var/log/nginx && \
chown -R nginx:nginx /etc/nginx/conf.d && \
touch /var/run/nginx.pid && \
chown -R nginx:nginx /var/run/nginx.pid

# Switch to non-root user
USER nginx

EXPOSE 8080
CMD ["nginx", "-g", "daemon off;"]
Loading