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
143 changes: 143 additions & 0 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
###############################################################################
# Kasal - Docker Compose for Local Development
#
# Usage:
# docker compose -f docker-compose.dev.yml up --build
# (or use the helper script: ./docker-dev.sh up)
#
# Services:
# - frontend : React dev server on http://localhost:3000
# - backend : FastAPI server on http://localhost:8000
# - postgres : PostgreSQL 16 on localhost:5432
###############################################################################

services:
# ---------------------------------------------------------------------------
# PostgreSQL Database
# ---------------------------------------------------------------------------
postgres:
image: pgvector/pgvector:pg16
container_name: kasal-postgres
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
POSTGRES_DB: ${POSTGRES_DB:-kasal}
ports:
- "${POSTGRES_PORT:-5432}:5432"
volumes:
- kasal_postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-kasal}"]
interval: 5s
timeout: 5s
retries: 5
networks:
- kasal-network

# ---------------------------------------------------------------------------
# Backend – FastAPI / Python
# ---------------------------------------------------------------------------
backend:
build:
context: ./src/backend
dockerfile: Dockerfile.dev
container_name: kasal-backend
restart: unless-stopped
ports:
- "${BACKEND_PORT:-8000}:8000"
volumes:
# Mount ONLY the source code sub-directory (not tests, migrations, etc.)
# This reduces the number of files the watcher needs to scan
- ./src/backend/src:/app/src
# Mount config files needed at the top level
- ./src/backend/pyproject.toml:/app/pyproject.toml:ro
- ./src/backend/uv.lock:/app/uv.lock:ro
- ./src/backend/alembic.ini:/app/alembic.ini:ro
- ./src/backend/migrations:/app/migrations
# Named volume keeps .venv inside the container (not on host)
- kasal_backend_venv:/app/.venv
# Persist logs on host for easy inspection
- ./src/backend/logs:/app/logs
environment:
# Database
DATABASE_TYPE: postgres
POSTGRES_SERVER: postgres
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
POSTGRES_DB: ${POSTGRES_DB:-kasal}
POSTGRES_PORT: "5432"
# Logging
KASAL_LOG_LEVEL: ${KASAL_LOG_LEVEL:-INFO}
KASAL_LOG_THIRD_PARTY: WARNING
# Telemetry
OTEL_SDK_DISABLED: "true"
CREWAI_DISABLE_TELEMETRY: "true"
# Engine
USE_NULLPOOL: "true"
# LLM API keys (pass through from host .env)
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
# Databricks (optional)
DATABRICKS_HOST: ${DATABRICKS_HOST:-}
DATABRICKS_TOKEN: ${DATABRICKS_TOKEN:-}
depends_on:
postgres:
condition: service_healthy
networks:
- kasal-network

# ---------------------------------------------------------------------------
# Frontend – React / Node
# ---------------------------------------------------------------------------
frontend:
build:
context: ./src/frontend
dockerfile: Dockerfile.dev
container_name: kasal-frontend
restart: unless-stopped
ports:
- "${FRONTEND_PORT:-3000}:3000"
volumes:
# Mount ONLY the src/ and public/ directories (the code you edit)
# This avoids the watcher scanning node_modules, build artifacts, etc.
- ./src/frontend/src:/app/src
- ./src/frontend/public:/app/public
# Mount config files read-only
- ./src/frontend/package.json:/app/package.json:ro
- ./src/frontend/tsconfig.json:/app/tsconfig.json:ro
- ./src/frontend/craco.config.js:/app/craco.config.js:ro
# Named volume keeps node_modules inside the container (not on host)
- kasal_frontend_node_modules:/app/node_modules
environment:
# Tell React dev server to talk to the backend container
REACT_APP_API_URL: http://localhost:${BACKEND_PORT:-8000}/api/v1
# Disable browser auto-open inside container
BROWSER: none
# Allow enough heap for webpack compilation
NODE_OPTIONS: "--max-old-space-size=2048"
# Tell webpack to only watch src/ (not node_modules or other dirs)
FAST_REFRESH: "true"
depends_on:
- backend
networks:
- kasal-network

# -----------------------------------------------------------------------------
# Named Volumes – dependencies live here, NOT on the host
# -----------------------------------------------------------------------------
volumes:
kasal_postgres_data:
driver: local
kasal_backend_venv:
driver: local
kasal_frontend_node_modules:
driver: local

# -----------------------------------------------------------------------------
# Network
# -----------------------------------------------------------------------------
networks:
kasal-network:
driver: bridge

156 changes: 156 additions & 0 deletions docker-dev.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
#!/usr/bin/env bash
###############################################################################
# Kasal – Docker Development Helper
#
# Usage:
# ./docker-dev.sh up Build & start all services
# ./docker-dev.sh down Stop and remove containers
# ./docker-dev.sh restart Restart all services
# ./docker-dev.sh logs Tail logs from all services
# ./docker-dev.sh logs <s> Tail logs for a specific service (backend|frontend|postgres)
# ./docker-dev.sh shell <s> Open a shell in a running service container
# ./docker-dev.sh build Rebuild images without starting
# ./docker-dev.sh clean Stop containers AND remove volumes (full reset)
# ./docker-dev.sh ps Show running containers
# ./docker-dev.sh test Run backend tests inside container
###############################################################################

set -euo pipefail

COMPOSE_FILE="docker-compose.dev.yml"
PROJECT_NAME="kasal"

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

# Ensure we run from the project root (where the compose file lives)
cd "$(dirname "$0")"

compose() {
podman-compose -f "$COMPOSE_FILE" -p "$PROJECT_NAME" "$@"
}

usage() {
cat <<EOF
${GREEN}Kasal Docker Development Helper${NC}

${BLUE}Usage:${NC} ./docker-dev.sh <command> [args]

${BLUE}Commands:${NC}
up Build images and start all services
down Stop and remove containers (keeps volumes)
restart Restart all services
logs [service] Tail logs (optionally for a single service)
shell <service> Open a shell in a running container
Services: backend, frontend, postgres
build Rebuild images without starting
clean Full reset: stop containers, remove volumes
ps Show running containers
test Run backend tests inside the container

${BLUE}Examples:${NC}
./docker-dev.sh up
./docker-dev.sh logs backend
./docker-dev.sh shell backend
./docker-dev.sh clean

EOF
}

case "${1:-help}" in
up)
echo -e "${GREEN}Building and starting Kasal dev environment...${NC}"
compose up --build -d
echo ""
echo -e "${GREEN}Services are starting up:${NC}"
echo -e " Frontend : ${BLUE}http://localhost:${FRONTEND_PORT:-3000}${NC}"
echo -e " Backend : ${BLUE}http://localhost:${BACKEND_PORT:-8000}${NC}"
echo -e " API Docs : ${BLUE}http://localhost:${BACKEND_PORT:-8000}/api-docs${NC}"
echo -e " Postgres : ${BLUE}localhost:${POSTGRES_PORT:-5432}${NC}"
echo ""
echo -e "${YELLOW}Run './docker-dev.sh logs' to follow output${NC}"
;;

down)
echo -e "${YELLOW}Stopping Kasal dev environment...${NC}"
compose down
echo -e "${GREEN}Done.${NC}"
;;

restart)
echo -e "${YELLOW}Restarting Kasal dev environment...${NC}"
compose down
compose up --build -d
echo -e "${GREEN}Restarted.${NC}"
;;

logs)
if [ -n "${2:-}" ]; then
compose logs -f "$2"
else
compose logs -f
fi
;;

shell)
SERVICE="${2:-}"
if [ -z "$SERVICE" ]; then
echo -e "${RED}Error: specify a service name (backend, frontend, postgres)${NC}"
exit 1
fi
case "$SERVICE" in
backend)
echo -e "${BLUE}Opening shell in backend container...${NC}"
compose exec backend bash
;;
frontend)
echo -e "${BLUE}Opening shell in frontend container...${NC}"
compose exec frontend sh
;;
postgres)
echo -e "${BLUE}Opening psql in postgres container...${NC}"
compose exec postgres psql -U "${POSTGRES_USER:-postgres}" -d "${POSTGRES_DB:-kasal}"
;;
*)
echo -e "${RED}Unknown service: $SERVICE${NC}"
echo "Available: backend, frontend, postgres"
exit 1
;;
esac
;;

build)
echo -e "${BLUE}Building images...${NC}"
compose build
echo -e "${GREEN}Build complete.${NC}"
;;

clean)
echo -e "${RED}Stopping containers and removing ALL volumes (full reset)...${NC}"
compose down -v
echo -e "${GREEN}Clean complete. Next 'up' will reinstall all dependencies.${NC}"
;;

ps)
compose ps
;;

test)
echo -e "${BLUE}Running backend tests...${NC}"
compose exec backend uv run pytest tests/ -v
;;

help|--help|-h)
usage
;;

*)
echo -e "${RED}Unknown command: $1${NC}"
usage
exit 1
;;
esac

43 changes: 43 additions & 0 deletions env.docker.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
###############################################################################
# Kasal – Docker Development Environment Variables
#
# Copy this file to .env in the project root:
# cp env.docker.example .env
#
# docker compose automatically loads .env from the same directory as the
# compose file. All values below have sensible defaults; only uncomment and
# change what you need.
###############################################################################

# =============================================================================
# Port Mapping (host ports)
# =============================================================================
# FRONTEND_PORT=3000
# BACKEND_PORT=8000
# POSTGRES_PORT=5432

# =============================================================================
# PostgreSQL Credentials
# =============================================================================
# POSTGRES_USER=postgres
# POSTGRES_PASSWORD=postgres
# POSTGRES_DB=kasal

# =============================================================================
# LLM / AI API Keys
# =============================================================================
# At least one key is required for AI features to work.
OPENAI_API_KEY=
ANTHROPIC_API_KEY=

# =============================================================================
# Databricks (optional – leave blank if not using Databricks)
# =============================================================================
# DATABRICKS_HOST=
# DATABRICKS_TOKEN=

# =============================================================================
# Logging
# =============================================================================
# KASAL_LOG_LEVEL=INFO

14 changes: 14 additions & 0 deletions src/backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.venv
__pycache__
*.pyc
*.pyo
logs
app.db
.pytest_cache
.mypy_cache
.ruff_cache
htmlcov
.coverage
.env*
.git

36 changes: 36 additions & 0 deletions src/backend/Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
FROM python:3.11-slim

# Prevent Python from writing .pyc files and enable unbuffered output
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

# Install system dependencies required by some Python packages
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
libpq-dev \
curl \
&& rm -rf /var/lib/apt/lists/*

# Install uv (the fast Python package manager used by the project)
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/

WORKDIR /app

# Copy only dependency files first for better layer caching
COPY pyproject.toml uv.lock ./

# Install dependencies into the project venv
# This layer is cached until pyproject.toml or uv.lock changes
RUN uv sync --frozen --no-install-project

# The source code will be bind-mounted at runtime via docker-compose
# so we don't COPY it here — that enables live hot reloading

# Create logs directory
RUN mkdir -p /app/logs

EXPOSE 8000

# Entrypoint: sync deps (picks up any new additions) then start with hot reload
CMD ["sh", "-c", "uv sync --frozen && uv run uvicorn src.main:app --reload --host 0.0.0.0 --port 8000"]

Loading