TOSCA is a modular Django API backend. It contains the Django admin and API surface for geodata provider management, catalog data, campaigns, events, feedback, geocontext, geostories, feature links, and authentication.
The local development stack runs three core services:
- Django API
- PostgreSQL/PostGIS
- GeoServer
The important rule: after setting up the environment, always initialize the Django project database before using the admin or API. Missing migrations are the most common cause of errors such as missing columns in admin pages.
Create the dev environment file, select it, then initialize the project:
cp .env.example .env.dev
make set-env ENV=dev
make which-env
make initialize-projectmake initialize-project does the first boot work:
- builds Docker images
- starts PostgreSQL/PostGIS, GeoServer, and Django
- runs Django migrations
- restarts Django after the database schema is ready
After that, create a Django admin user if needed:
make django-createsuperuserThen open:
- Django admin:
http://localhost:8000/admin/ - GeoServer:
http://localhost:8080/geoserver/web/
Start services:
make upStop services:
make downFollow logs:
make logs
make django-logsShow running containers:
make psRebuild after dependency, Dockerfile, or image changes:
make rebuildThis repository is a Django project first. Treat migrations as part of the normal development loop.
After pulling code, switching branches, or enabling a feature that adds model fields:
make django-migrateFor one app only:
make django-migrate APP=geodata_providersTo create migrations after model changes:
make django-makemigrations
make django-migrateIf the admin raises a database error like column ... does not exist, first
check migration state and apply pending migrations:
docker compose --env-file .env.dev -f docker-compose-dev.yml exec django \
uv run python manage.py showmigrations geodata_providers
make django-migratemake django-shell
make django-cmd
make django-migrate
make django-makemigrations
make django-createsuperuser
make django-test
make django-test-integrationFull command list:
make helpThe Makefile uses .make.env to remember the active environment.
Development:
make set-env ENV=dev
make which-envProduction:
cp .env.example .env.prod
make set-env ENV=prod
make which-envEnvironment resolution:
devuses.env.devanddocker-compose-dev.ymlproduses.env.prodanddocker-compose-prod.yml
Keep ENV, ENV_TYPE, ENV_FILE, ports, passwords, and public URLs aligned in
the selected env file.
Only .env.example is committed, and it holds placeholders only (every
credential is CHANGE_ME_...). .env, .env.dev, .env.prod, and
.env.test are gitignored deliberately — none of them should ever be
committed, since they carry real (even if dev-only) credentials.
A pre-commit hook enforces this: it blocks committing any .env* file other
than .env.example. Set it up once per clone:
uv sync --group dev
uv run pre-commit installWhen using Keycloak/OIDC, the Keycloak client's valid redirect URIs and the realm's browser security headers must both allow the public Django callback origin.
Chrome may stop after the Keycloak login POST if the realm
Content-Security-Policy does not allow the Django site in form-action. In
Network this looks like Keycloak returning 302 Found with a Location header
pointing at /accounts/oidc/.../callback/, but Chrome never makes the callback
request. Firefox may appear more forgiving, and refreshing can work because the
Keycloak SSO session already exists.
Check the Keycloak realm setting:
Realm settings -> Security defenses -> Headers -> Content-Security-Policy
The form-action directive must include the Django public origin. For multiple
deployment subdomains, keep the policy explicit where possible:
form-action 'self' https://<django-public-origin> https://<other-allowed-origin>
If all trusted deployments live below the same controlled DNS zone, a wildcard can be used, but it is broader than an explicit allowlist:
form-action 'self' https://*.example.org
Development and production intentionally use different GeoServer image sources.
docker-compose-dev.ymlbuilds GeoServer from the local submodule atdocker/geoserver_docker. Use this when changing GeoServer Docker scripts or templates.docker-compose-prod.ymluses the production GeoServer image published from GitHub Container Registry.
GeoServer Docker changes should be validated in dev, merged in
docker/geoserver_docker, then consumed through the CI-built production image.
GeoServer admin credentials are configured in the active env file:
GEOSERVER_ADMIN_USER=admin2
GEOSERVER_ADMIN_PASSWORD=geoserver2On first startup the container uses GeoServer's built-in admin/geoserver only
as a bootstrap credential. It then creates or updates GEOSERVER_ADMIN_USER,
grants ADMIN, and disables the built-in admin user by default when the
configured user is not admin.
The JDBC feature flags are related but independent:
GEOSERVER_ENABLE_JDBC_ROLE=true
GEOSERVER_ENABLE_JDBC_AUTH=false
GEOSERVER_ENABLE_JDBC_CONFIG=falseGEOSERVER_ENABLE_JDBC_ROLE=trueenables JDBC role service files and role mapping into Postgre.GEOSERVER_ENABLE_JDBC_AUTH=trueenables JDBC user/group service and auth provider. It implies role support.GEOSERVER_ENABLE_JDBC_CONFIG=trueenables the advanced JDBCConfig catalog path.
After services are running, apply GeoServer JDBC security/config files when the selected setup needs them:
make jdbc-settings-activationFor JDBC role/auth UI steps, see:
docker/geoserver_docker/readme_jdbc.md
Run the normal test suite:
make django-testRun tests for a single app:
make django-test APP=geodata_providersRun integration tests that require live services:
make django-test-integrationThe production stack is defined in docker-compose-prod.yml.
Services:
db: PostgreSQL/PostGIS for Django.geoserver: production GeoServer image.django: Gunicorn-backed Django API.web: SPA Nginx container. SetWEB_IMAGEto the built SPA image.nginx: public reverse proxy for/api/,/admin/,/accounts/,/geoserver/,/media/,/static/, and the SPA shell.
Start production:
cp .env.example .env.prod
make set-env ENV=prod
make upIn a real deployment, set WEB_IMAGE to an immutable image from the SPA
pipeline:
WEB_IMAGE=registry.example.com/tosca-web:2026-05-21Required Django media/static settings for production:
DJANGO_STATIC_URL=/static/
DJANGO_STATIC_ROOT=/app/staticfiles
DJANGO_MEDIA_URL=/media/
DJANGO_MEDIA_ROOT=/app/mediaProduction Django writes uploaded media and collected static files to shared Docker volumes. Nginx serves those files to browsers:
Django /app/media -> media_files -> Nginx /usr/share/nginx/media
Django /app/staticfiles -> static_files -> Nginx /usr/share/nginx/staticfiles
The API may return media paths such as /media/geocontext/editorjs/image.png.
In same-domain deployments those paths work directly. In separate SPA/API host
deployments, the SPA should prepend the public API or asset host.
To intentionally remove local database and GeoServer volumes:
make rmvolumes
make initialize-projectThis deletes local data. Use it only when you really want a fresh stack.