Add Docker support with Gunicorn, SQLite volume persistence, and optional PostgreSQL
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
10
.dockerignore
Normal file
10
.dockerignore
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
.env
|
||||||
|
.venv
|
||||||
|
__pycache__
|
||||||
|
*.py[cod]
|
||||||
|
*.sqlite3
|
||||||
|
db.sqlite3
|
||||||
|
*.log
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
README.md
|
||||||
@@ -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 (the accounting frontend dev server runs on port 5176)
|
||||||
CORS_ALLOW_ALL_ORIGINS=True
|
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
36
Dockerfile
Normal 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", "-"]
|
||||||
24
README.md
24
README.md
@@ -52,3 +52,27 @@ default company settings/account mapping. No demo business data is created.
|
|||||||
The frontend logs in via the FunZone admin endpoint
|
The frontend logs in via the FunZone admin endpoint
|
||||||
(`/api/auth/token/admin/`) and reuses the returned access token for this service.
|
(`/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.
|
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.
|
||||||
|
|||||||
@@ -71,12 +71,17 @@ if DATABASE_URL:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else:
|
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:
|
else:
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
'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
53
docker-compose.yml
Normal 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
14
docker-entrypoint.sh
Normal 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 "$@"
|
||||||
@@ -9,3 +9,6 @@ python-decouple==3.8
|
|||||||
|
|
||||||
# Optional PostgreSQL driver (SQLite is used by default)
|
# Optional PostgreSQL driver (SQLite is used by default)
|
||||||
psycopg2-binary==2.9.9
|
psycopg2-binary==2.9.9
|
||||||
|
|
||||||
|
# Production server
|
||||||
|
gunicorn==21.2.0
|
||||||
|
|||||||
Reference in New Issue
Block a user