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:
parent
1aa676fc98
commit
1f6b797909
9 changed files with 297 additions and 55 deletions
42
.dockerignore
Normal file
42
.dockerignore
Normal 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
41
.env.docker
Normal 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
3
.gitignore
vendored
|
|
@ -54,6 +54,9 @@ frontend/pnpm-lock.yaml
|
|||
logs/
|
||||
*.log
|
||||
|
||||
# Docker local overrides
|
||||
.env.docker.local
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
|
|
|||
54
Dockerfile
54
Dockerfile
|
|
@ -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
|
||||
|
|
|
|||
55
README.md
55
README.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
22
docker/backend/entrypoint.sh
Executable 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
|
||||
24
docker/postgres/init-rls-user.sql
Normal file
24
docker/postgres/init-rls-user.sql
Normal 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
14
frontend/Dockerfile
Normal 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"]
|
||||
Loading…
Reference in a new issue