Add Docker support with Gunicorn, SQLite volume persistence, and optional PostgreSQL

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Shayan Azadi
2026-07-01 00:32:25 +03:30
parent 066276743a
commit 5dd29c41d6
8 changed files with 151 additions and 3 deletions

10
.dockerignore Normal file
View File

@@ -0,0 +1,10 @@
.env
.venv
__pycache__
*.py[cod]
*.sqlite3
db.sqlite3
*.log
.git
.gitignore
README.md

View File

@@ -10,4 +10,7 @@ ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0,testserver
# CORS (the accounting frontend dev server runs on port 5176)
CORS_ALLOW_ALL_ORIGINS=True
CORS_ALLOWED_ORIGINS=http://localhost:5176,http://127.0.0.1:5176
CORS_ALLOWED_ORIGINS=http://localhost:5176,http://127.0.0.1:5176,http://localhost:3006,http://127.0.0.1:3006
# Docker: persist SQLite in a named volume
# SQLITE_PATH=/data/db.sqlite3

36
Dockerfile Normal file
View File

@@ -0,0 +1,36 @@
# FunZone Accounting Backend Dockerfile
FROM python:3.11-slim
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV DEBIAN_FRONTEND=noninteractive
WORKDIR /app
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
postgresql-client \
build-essential \
libpq-dev \
curl \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt /app/
RUN pip install --no-cache-dir -r requirements.txt
COPY . /app/
RUN mkdir -p /data \
&& adduser --disabled-password --gecos '' appuser \
&& chown -R appuser:appuser /app /data \
&& chmod +x /app/docker-entrypoint.sh
USER appuser
EXPOSE 8010
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
CMD curl -f http://localhost:8010/health || exit 1
ENTRYPOINT ["/app/docker-entrypoint.sh"]
CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8010", "--workers", "2", "--timeout", "120", "--access-logfile", "-", "--error-logfile", "-"]

View File

@@ -52,3 +52,27 @@ default company settings/account mapping. No demo business data is created.
The frontend logs in via the FunZone admin endpoint
(`/api/auth/token/admin/`) and reuses the returned access token for this service.
Make sure `SECRET_KEY` here is identical to the FunZone backend's.
## Docker
SQLite (default, zero extra services):
```bash
docker compose up -d --build
```
The API listens on **http://localhost:8010** (`/health`, `/api/accounting/...`).
Set `SECRET_KEY` to match the FunZone backend before starting:
```bash
SECRET_KEY=your-shared-secret docker compose up -d --build
```
Optional PostgreSQL instead of SQLite:
```bash
docker compose --profile postgres up -d --build
```
Then set `DATABASE_URL=postgresql://accounting_user:accounting_password@accounting-db:5432/accounting_db` in your environment or `.env` file.

View File

@@ -71,12 +71,17 @@ if DATABASE_URL:
}
}
else:
DATABASES = {'default': {'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3'}}
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': config('SQLITE_PATH', default=str(BASE_DIR / 'db.sqlite3')),
}
}
else:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
'NAME': config('SQLITE_PATH', default=str(BASE_DIR / 'db.sqlite3')),
}
}

53
docker-compose.yml Normal file
View File

@@ -0,0 +1,53 @@
services:
accounting-backend:
build:
context: .
dockerfile: Dockerfile
container_name: funzone_accounting_backend
ports:
- "127.0.0.1:8010:8010"
environment:
SECRET_KEY: ${SECRET_KEY:-your-super-secret-key-change-in-production}
DEBUG: ${DEBUG:-False}
ALLOWED_HOSTS: ${ALLOWED_HOSTS:-localhost,127.0.0.1,accounting-backend}
CORS_ALLOW_ALL_ORIGINS: ${CORS_ALLOW_ALL_ORIGINS:-True}
CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-http://localhost:3006,http://localhost:5176,http://127.0.0.1:3006,http://127.0.0.1:5176}
SQLITE_PATH: /data/db.sqlite3
DATABASE_URL: ${DATABASE_URL:-}
DB_HOST: accounting-db
POSTGRES_USER: ${POSTGRES_USER:-accounting_user}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-accounting_password}
POSTGRES_DB: ${POSTGRES_DB:-accounting_db}
volumes:
- accounting_sqlite:/data
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8010/health"]
interval: 30s
timeout: 10s
start_period: 40s
retries: 3
accounting-db:
image: postgres:16-alpine
container_name: funzone_accounting_postgres
profiles:
- postgres
environment:
POSTGRES_DB: ${POSTGRES_DB:-accounting_db}
POSTGRES_USER: ${POSTGRES_USER:-accounting_user}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-accounting_password}
volumes:
- accounting_postgres:/var/lib/postgresql/data
ports:
- "127.0.0.1:5434:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-accounting_user} -d ${POSTGRES_DB:-accounting_db}"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
accounting_sqlite:
accounting_postgres:

14
docker-entrypoint.sh Normal file
View File

@@ -0,0 +1,14 @@
#!/bin/sh
set -e
if [ -n "$DATABASE_URL" ]; then
until PGPASSWORD="${POSTGRES_PASSWORD:-accounting_password}" psql -h "${DB_HOST:-accounting-db}" -U "${POSTGRES_USER:-accounting_user}" -d "${POSTGRES_DB:-accounting_db}" -c '\q' 2>/dev/null; do
>&2 echo "PostgreSQL is unavailable - sleeping"
sleep 1
done
>&2 echo "PostgreSQL is up"
fi
python manage.py migrate --noinput
exec "$@"

View File

@@ -9,3 +9,6 @@ python-decouple==3.8
# Optional PostgreSQL driver (SQLite is used by default)
psycopg2-binary==2.9.9
# Production server
gunicorn==21.2.0