115 lines
7 KiB
Markdown
115 lines
7 KiB
Markdown
# Docker
|
|
|
|
`docker-compose.yml`, at the repository root, runs the full stack: database, cache/broker,
|
|
API, background workers and frontend, as six services from one file. This page documents that
|
|
file service by service. For the fastest path to a running instance with demo data loaded, see
|
|
[Docker quick start](../getting-started/docker-quick-start.md); this page instead covers what to
|
|
configure, how data is persisted, and how to upgrade an instance you intend to keep running.
|
|
|
|
**This compose file is a development setup, not a hardened production one.** Its `backend`
|
|
service bind-mounts the repository (`./backend:/app`) and its entrypoint
|
|
(`docker/backend/entrypoint.sh`) ends by running `python manage.py runserver 0.0.0.0:8000`, the
|
|
Django development server, not Gunicorn. `.env.docker` also ships `DEBUG=True`. Running it as-is
|
|
gets every service talking to each other correctly; it does not by itself satisfy the
|
|
requirements in [Production deployment](production-deploy.md).
|
|
|
|
## The services
|
|
|
|
`docker-compose.yml` defines:
|
|
|
|
| Service | Image / build | Host port | Purpose |
|
|
|---|---|---|---|
|
|
| `db` | `postgres:16-alpine` | `5432` | The PostgreSQL database, `crm_db`. Row-Level Security policies applied by Django migrations rely on this being real PostgreSQL. See [PostgreSQL and RLS](postgresql-and-rls.md). |
|
|
| `redis` | `redis:7-alpine` | `6379` | Celery broker and result backend (`CELERY_BROKER_URL` / `CELERY_RESULT_BACKEND` both point here). |
|
|
| `backend` | built from the repo-root `Dockerfile` | `8000` | The Django API, served via `manage.py runserver` in this compose file (see the warning above). |
|
|
| `celery-worker` | same image as `backend` |, (no published port) | Runs `celery -A crm worker --loglevel=info`; executes background tasks such as outbound email and RLS-context-aware jobs in `backend/common/tasks.py`. |
|
|
| `celery-beat` | same image as `backend` |, (no published port) | Runs `celery -A crm beat --loglevel=info`; fires the periodic tasks registered in `backend/crm/celery.py` (recurring invoices, overdue/expiry checks, SLA scanning, cleanup jobs) on their schedules. |
|
|
| `frontend` | built from `frontend/Dockerfile` | `5181` | The SvelteKit app, served via `pnpm dev --host 0.0.0.0`, also a dev server, not a production build. |
|
|
|
|
`backend`, `celery-worker` and `celery-beat` each declare `depends_on: db (service_healthy), redis
|
|
(service_healthy)`, using the `pg_isready` and `redis-cli ping` healthchecks defined on those two
|
|
services, so they wait for both to actually accept connections rather than just for their
|
|
containers to start.
|
|
|
|
## Configuration
|
|
|
|
Every service loads two env files, in order: `.env.docker` (checked into the repository, working
|
|
defaults) and then `.env.docker.local` (optional, gitignored, `required: false`, create it next
|
|
to `.env.docker` to override specific values without touching the tracked file; values there win).
|
|
|
|
The variables `.env.docker` sets: `SECRET_KEY` (an insecure placeholder. See
|
|
[Production deployment](production-deploy.md) before this instance is anything but local),
|
|
`DEBUG=True`, `ENV_TYPE=dev`, `ALLOWED_HOSTS=localhost,127.0.0.1,backend`,
|
|
`DOMAIN_NAME` (`http://localhost:8000`), the `DBNAME`/`DBUSER`/`DBPASSWORD`/`DBHOST`/
|
|
`DBPORT` the `backend` and Celery containers use to reach `db`, `POSTGRES_DB`/`POSTGRES_USER`/
|
|
`POSTGRES_PASSWORD` (used only by the `db` service itself, to create the database and its own
|
|
superuser role. See [PostgreSQL and RLS](postgresql-and-rls.md) for why the app never connects
|
|
as this role), `CELERY_BROKER_URL`/`CELERY_RESULT_BACKEND` (both pointing at `redis`),
|
|
`DEFAULT_FROM_EMAIL`/`ADMIN_EMAIL`/`ADMIN_PASSWORD`, `CORS_ALLOW_ALL=True`,
|
|
`GOOGLE_CLIENT_ID`/`GOOGLE_CLIENT_SECRET` (blank. Google sign-in stays disabled until both
|
|
are set), and `PUBLIC_DJANGO_API_URL=http://backend:8000`, the compose service name the
|
|
frontend's server-side code uses to reach the API.
|
|
|
|
## Running it
|
|
|
|
From the repository root (the directory containing `docker-compose.yml` and `Dockerfile`):
|
|
|
|
```bash
|
|
docker compose up --build
|
|
```
|
|
|
|
This requires the Compose v2 plugin (the `docker compose` subcommand, not the standalone
|
|
`docker-compose` binary). On first boot, `docker/postgres/init-rls-user.sql` runs automatically
|
|
via PostgreSQL's `/docker-entrypoint-initdb.d/` mechanism and creates the non-superuser `crm_user`
|
|
role that `backend` actually connects as. `docker/backend/entrypoint.sh` then waits for
|
|
PostgreSQL to accept connections, runs `manage.py migrate --noinput`, runs
|
|
`manage.py create_default_admin`, runs `manage.py collectstatic --noinput`, and finally starts the
|
|
dev server. Every one of those steps runs again on every container restart, not just the first.
|
|
|
|
Run any one-off management command inside the running `backend` container with `docker compose
|
|
exec`, for example:
|
|
|
|
```bash
|
|
docker compose exec backend python manage.py migrate
|
|
docker compose exec backend python manage.py manage_rls --status
|
|
```
|
|
|
|
## Persisting data
|
|
|
|
`docker-compose.yml` declares two named volumes:
|
|
|
|
| Volume | Mounted at | Holds |
|
|
|---|---|---|
|
|
| `postgres_data` | `/var/lib/postgresql/data` on `db` | The actual database: every org, record, and RLS-protected row. This is the volume that must survive a rebuild. |
|
|
| `frontend_node_modules` | `/app/node_modules` on `frontend` | Installed frontend dependencies, a build cache, not user data. Safe to delete; `pnpm install` recreates it. |
|
|
|
|
Everything else is a bind mount of source code from the host, not a Docker-managed volume:
|
|
`./backend:/app` on `backend`/`celery-worker`/`celery-beat`, and `./frontend:/app` on `frontend`.
|
|
One consequence worth knowing: with `ENV_TYPE=dev` (the shipped default), Django's `MEDIA_ROOT` is
|
|
`BASE_DIR/media`, which resolves inside the container to `/app/media`, and because `/app` is the
|
|
bind-mounted `./backend` directory, uploaded files (invoice attachments, case attachments, and so
|
|
on) land in `backend/media/` on the host automatically. They persist across container rebuilds for
|
|
the same reason the source code does, without needing a dedicated named volume, but they are
|
|
**not** covered by a `postgres_data`-style volume, so back up `backend/media/` alongside the
|
|
database if you rely on uploaded files.
|
|
|
|
`docker compose down` removes containers and the default network but leaves named volumes intact;
|
|
only `docker compose down -v`, or removing `postgres_data`/`frontend_node_modules` explicitly,
|
|
deletes them.
|
|
|
|
## Upgrading
|
|
|
|
To move to a newer version of the application: pull the new source, then rebuild and restart:
|
|
|
|
```bash
|
|
docker compose up --build
|
|
```
|
|
|
|
Because `entrypoint.sh` runs `manage.py migrate --noinput` on every container start (not only the
|
|
first), rebuilding and restarting is sufficient to bring the schema up to date. There is no
|
|
separate migration step to remember. `collectstatic` also reruns on every start, so updated static
|
|
assets are picked up automatically.
|
|
|
|
Back up the `postgres_data` volume (and `backend/media/`, per the note above) before upgrading
|
|
across any change you're not certain is backward compatible with your existing data, nothing in
|
|
this compose setup takes an automatic backup for you.
|