feat: add Docker Compose setup for local development

Dockerize the full stack so developers can run everything with
`docker compose up`. Replaces the outdated Dockerfile and
docker-compose.yml with a working 6-service setup: PostgreSQL,
Redis, Django backend, Celery worker, Celery beat, and SvelteKit
frontend.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
ashwin31 2026-02-17 10:01:50 +05:30
parent 1aa676fc98
commit 1f6b797909
9 changed files with 297 additions and 55 deletions

42
.dockerignore Normal file
View file

@ -0,0 +1,42 @@
.git
.gitignore
.github
.vscode
.idea
# Python
__pycache__
*.pyc
*.pyo
venv/
env/
.venv/
*.egg-info
.pytest_cache/
.coverage
htmlcov/
# Node / Frontend (built separately)
frontend/node_modules/
frontend/.svelte-kit/
frontend/build/
# Mobile app
mobile/
# Docker
docker-compose.yml
.dockerignore
# Env files (injected at runtime)
.env
.env.*
backend/.env
backend/.env.*
# Misc
*.md
*.log
.DS_Store
Thumbs.db
.playwright-mcp

41
.env.docker Normal file
View file

@ -0,0 +1,41 @@
# Docker development environment variables
# Copy to .env.docker.local and customize if needed
# Django
SECRET_KEY=django-insecure-docker-dev-key-change-in-production
DEBUG=True
ENV_TYPE=dev
ALLOWED_HOSTS=localhost,127.0.0.1,backend
DOMAIN_NAME=http://localhost:8000
SWAGGER_ROOT_URL=http://localhost:8000
# Database (must match docker-compose db service)
DBNAME=crm_db
DBUSER=crm_user
DBPASSWORD=crm_password
DBHOST=db
DBPORT=5432
# Postgres superuser (used by db service to create the database)
POSTGRES_DB=crm_db
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres_password
# Celery / Redis
CELERY_BROKER_URL=redis://redis:6379/0
CELERY_RESULT_BACKEND=redis://redis:6379/0
# Email (console backend in dev — emails print to stdout)
DEFAULT_FROM_EMAIL=noreply@localhost
ADMIN_EMAIL=admin@localhost
# CORS
CORS_ALLOW_ALL=True
# Google OAuth (leave blank to disable)
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_REDIRECT_URI=
# Frontend
PUBLIC_DJANGO_API_URL=http://localhost:8000

3
.gitignore vendored
View file

@ -54,6 +54,9 @@ frontend/pnpm-lock.yaml
logs/
*.log
# Docker local overrides
.env.docker.local
# OS
.DS_Store
Thumbs.db

View file

@ -1,41 +1,27 @@
FROM ubuntu:24.04
FROM python:3.12-slim-bookworm
# invalidate cache
ARG APP_NAME
# Prevent Python from buffering stdout/stderr (useful for Docker logs)
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
# test arg
RUN test -n "$APP_NAME"
# install system packages
RUN apt-get update -y
RUN apt-get install -y \
python3-pip \
python3-venv \
build-essential \
# Install system dependencies for WeasyPrint (cairo, pango) and PostgreSQL
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq-dev \
libmariadbclient-dev \
libjpeg62-dev \
zlib1g-dev \
libwebp-dev \
curl \
vim \
net-tools
libcairo2 \
libpango-1.0-0 \
libpangocairo-1.0-0 \
libgdk-pixbuf2.0-0 \
libffi-dev \
shared-mime-info \
&& rm -rf /var/lib/apt/lists/*
# setup user
RUN useradd -ms /bin/bash ubuntu
USER ubuntu
WORKDIR /app
# install app
RUN mkdir -p /home/ubuntu/"$APP_NAME"/"$APP_NAME"
WORKDIR /home/ubuntu/"$APP_NAME"/"$APP_NAME"
COPY . .
RUN python3 -m venv ../venv
RUN . ../venv/bin/activate
RUN /home/ubuntu/"$APP_NAME"/venv/bin/pip install -U pip
RUN /home/ubuntu/"$APP_NAME"/venv/bin/pip install -r requirements.txt
RUN /home/ubuntu/"$APP_NAME"/venv/bin/pip install gunicorn
# Install Python dependencies (layer cached)
COPY backend/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# setup path
ENV PATH="${PATH}:/home/ubuntu/$APP_NAME/$APP_NAME/scripts"
# Copy backend source
COPY backend/ .
USER ubuntu
EXPOSE 8000

View file

@ -120,12 +120,49 @@ celery -A crm worker --loglevel=INFO
## Docker Setup
Run the full stack (backend, frontend, PostgreSQL, Redis, Celery) with a single command:
```bash
# Build and run with Docker Compose
docker build -t bottlecrm:latest -f docker/dockerfile .
docker-compose -f docker/docker-compose.yml up
# Start all services (first run will build images)
docker compose up --build
# Create an admin user
docker compose exec backend python manage.py createsuperuser
# (Optional) Load sample data
docker compose exec backend python manage.py seed_data --email admin@example.com
```
Once running:
- **Frontend**: http://localhost:5173
- **API / Swagger**: http://localhost:8000/swagger-ui/
- **PostgreSQL**: localhost:5432
- **Redis**: localhost:6379
### Daily workflow
```bash
docker compose up # start all services (code changes auto-reload)
docker compose down # stop all services
docker compose down -v # stop and delete all data (full reset)
```
### Running commands inside containers
```bash
docker compose exec backend python manage.py migrate
docker compose exec backend python -m pytest
docker compose exec backend python manage.py manage_rls --status
```
### Custom environment overrides
The default env vars live in `.env.docker` (committed). To override locally without touching git:
1. Copy `.env.docker` to `.env.docker.local`
2. Edit values as needed
3. Update `env_file` in `docker-compose.yml` to point to `.env.docker.local`
## Project Structure
```
@ -146,8 +183,16 @@ Django-CRM/
│ │ └── routes/ # SvelteKit routes
│ │ ├── (app)/ # Authenticated app routes
│ │ └── (no-layout)/ # Auth pages (login, etc.)
│ └── static/ # Static assets
└── docker/ # Docker configuration
│ ├── static/ # Static assets
│ └── Dockerfile # Frontend dev container
├── docker/ # Docker support files
│ ├── backend/
│ │ └── entrypoint.sh # DB wait + migrate + runserver
│ └── postgres/
│ └── init-rls-user.sql # Creates non-superuser for RLS
├── Dockerfile # Backend / Celery image
├── docker-compose.yml # Full-stack dev environment
└── .env.docker # Docker env vars (dev defaults)
```
## Multi-Tenancy & Security

View file

@ -1,19 +1,84 @@
version: '2.0'
services:
db:
image: postgres
# volumes:
# - /home/db:/var/lib/postgresql/data
env_file:
- ./db.env
web:
build: .
image: micropyramid/django-crm:1
env_file:
- ./db.env
image: postgres:16-alpine
env_file: .env.docker
volumes:
- postgres_data:/var/lib/postgresql/data
- ./docker/postgres/init-rls-user.sql:/docker-entrypoint-initdb.d/init-rls-user.sql:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
ports:
- "8001:8000"
- "5432:5432"
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
ports:
- "6379:6379"
backend:
build:
context: .
dockerfile: Dockerfile
env_file: .env.docker
volumes:
- ./backend:/app
- ./docker/backend/entrypoint.sh:/entrypoint.sh:ro
ports:
- "8000:8000"
depends_on:
- db
db:
condition: service_healthy
redis:
condition: service_healthy
command: /bin/bash /entrypoint.sh
celery-worker:
build:
context: .
dockerfile: Dockerfile
env_file: .env.docker
volumes:
- ./backend:/app
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
command: celery -A crm worker --loglevel=info
celery-beat:
build:
context: .
dockerfile: Dockerfile
env_file: .env.docker
volumes:
- ./backend:/app
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
command: celery -A crm beat --loglevel=info
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
env_file: .env.docker
volumes:
- ./frontend:/app
- frontend_node_modules:/app/node_modules
ports:
- "5173:5173"
volumes:
postgres_data:
frontend_node_modules:

22
docker/backend/entrypoint.sh Executable file
View file

@ -0,0 +1,22 @@
#!/bin/bash
set -e
echo "Waiting for PostgreSQL..."
while ! python -c "
import socket, os
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((os.environ['DBHOST'], int(os.environ['DBPORT'])))
s.close()
" 2>/dev/null; do
sleep 1
done
echo "PostgreSQL is ready."
echo "Running migrations..."
python manage.py migrate --noinput
echo "Collecting static files..."
python manage.py collectstatic --noinput
echo "Starting development server..."
exec python manage.py runserver 0.0.0.0:8000

View file

@ -0,0 +1,24 @@
-- Create the application database user (non-superuser) for RLS enforcement.
-- This script runs automatically on first `docker compose up` via
-- PostgreSQL's /docker-entrypoint-initdb.d/ mechanism.
-- The database itself (crm_db) is created by POSTGRES_DB env var.
-- We just need to create the app user and grant privileges.
DO $$
BEGIN
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'crm_user') THEN
CREATE ROLE crm_user WITH LOGIN PASSWORD 'crm_password';
END IF;
END
$$;
-- Grant privileges on the database
GRANT ALL PRIVILEGES ON DATABASE crm_db TO crm_user;
-- Allow crm_user to create schemas and objects in the public schema
\connect crm_db;
GRANT ALL ON SCHEMA public TO crm_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO crm_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO crm_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON FUNCTIONS TO crm_user;

14
frontend/Dockerfile Normal file
View file

@ -0,0 +1,14 @@
FROM node:22-slim
# Enable pnpm via corepack
RUN corepack enable && corepack prepare pnpm@latest --activate
WORKDIR /app
# Install dependencies (layer cached)
COPY package.json pnpm-lock.yaml* ./
RUN pnpm install --frozen-lockfile || pnpm install
EXPOSE 5173
CMD ["pnpm", "dev", "--host", "0.0.0.0"]