dsp-core¶
This module is part of the DSP β see the full documentation in dsp-docs. The information below covers this module only.
Purpose¶
The dsp-core is the DSP Docker Compose orchestration hub. It does not contain application/domain code β its responsibility is to prepare and start infrastructure (databases, GeoServers, gateway, migration and geo-file jobs) and orchestrate builds of the other modules.
flowchart TD
core["dsp-core"]
be["dsp-backend"]
fe["dsp-frontend"]
job["dsp-job-data-migration"]
geoFile["dsp-job-geo-file-generation"]
storage["dsp-object-storage (SeaweedFS)"]
gs["2 GeoServers + 2 Postgres/PostGIS databases"]
gw["dsp-gateway"]
core --> be
core --> fe
core --> job
core --> geoFile
core --> storage
core --> gs
core --> gw
Responsibilities¶
- Sample adopter configuration.
- Sample About page content (
config/about/). - Database initialization SQL.
- GeoServer Exhibition (map) and GeoServer Download (export WFS).
- nginx gateway (
dsp-gateway) as the single entry point for the stack. - SeaweedFS object storage (
dsp-object-storage, profileobject-storage) and geo-file job (dsp-job-geo-file-generation, same profile) β required for a real adopter; Brazil Demo does not start these services. - Migration job (
dsp-job-migration, profilemigration). - Three operational scripts:
./config.sh,./setup.sh,./start.sh. - Automatic clone of sibling repositories when missing (with a folder-structure preview before confirmation). The script looks in the short folder (
backendand the equivalents) and, if the code is not there, in the repository-named folder (dsp-*). When both are missing, the download creates thedsp-*folder../config.shalso clones the job if it is missing.
Prerequisites¶
| Requirement | Detail |
|---|---|
| Bash shell | Scripts (config.sh, setup.sh, start.sh) are plain bash. Native on Linux and macOS; on Windows requires WSL2 (no native PowerShell/cmd support). |
| Git | Clone missing sibling repositories (automatic via scripts or manual); only needed if repos do not exist yet |
| Docker 24+ with Compose v2 | Used to start databases, GeoServer, and other modules |
| Python 3 | Used by the ./config.sh wizard |
The three scripts¶
./config.sh¶
Interactive wizard that writes config/adopter/adopter-config.yaml and, on reapply, generates operational files (list below). Entry: ./config.sh (no arguments).
config/installation/installation-config.jsonβ hierarchy, screens, KPIs,map.initialView, AOI detail panel.config/map/mapLayersConfig.jsonβ WMS groups and layers, SRIDs.config/downloads/downloadThemesConfig.jsonβ Downloads screen themes.config/about/about-config.jsonβ About page index (when enabled); Markdown inconfig/about/.config/Job-Data-Migration/application/application.yamlβ datasources and ETL mapping.config/Job-Geo-File-Generation/application/application.yamlβ object storage (SeaweedFS); no cron in YAML..envβDSP_SOURCE_*,DSP_OBJECT_STORAGE_*, and other keys the wizard fills.
These operational artifacts must not be edited manually β they are derived and regenerated on every reapply of ./config.sh. Always adjust config/adopter/adopter-config.yaml (wizard or editor) and then reapply.
Run with no arguments (./config.sh). Before the wizard, the script looks for each module in the short folder (backend, frontend, job-data-migration, job-geo-file-generation) and, if the code is not there, in the dsp-* folder. When both are missing, it offers to clone into the repository-named folder.
The wizard does not ask for batch job schedule nor write DSP_MIGRATION_CRON or DSP_GEO_FILE_GENERATION_CRON. Reapply (option 1) does not change crons in .env.
First run (no adopter-config.yaml yet): the script asks how to configure:
| Option | Action |
|---|---|
| 1 | Guided configuration β step-by-step wizard |
| 2 | Existing configuration β place your adopter-config.yaml at the indicated path and reapply later (option 1 in the menu below) |
Later runs (when YAML already exists):
| Option | Action |
|---|---|
| 1 | Reapply existing configuration without going through the wizard again |
| 2 | Edit existing configuration (reopens the wizard prefilled with current values) |
| 3 | Start over from template (adopter-config.yaml.example), discarding the current file |
Two ways to configure
You can follow the step-by-step wizard (recommended β each question explains the field and where the value is used, showing current/default in brackets and keeping it if you press Enter), or edit directly config/adopter/adopter-config.yaml in a text editor, using config/adopter/adopter-config.yaml.example as a structure reference. After editing the adopter, run ./config.sh and choose 1 β Reapply to regenerate operational JSON/YAML. Do not edit installation-config.json, mapLayersConfig.json, downloadThemesConfig.json, or job application.yaml files by hand.
Rebuild after changing configuration
Operational files generated in config/ are copied into Docker images at build (select-runtime-config.sh + dsp_config context). After ./config.sh, run ./setup.sh or ./start.sh to rebuild containers that consume that configuration (backend, GeoServers, migration job).
The wizard is split into 6 stages. In each, the operator answers guided questions (field and impact explained) until adopter configuration and customization are covered:
| Stage | What is configured | Impact |
|---|---|---|
| 1 β Source database | Source JDBC URL, read user/password (DSP_SOURCE_DB_USER / DSP_SOURCE_DB_PASSWORD) |
Migration job and .env |
| 2 β Tables, columns, and layers | For L1/L2/L3/AOI: table, PK (one column; composite not supported), parent_key (L2/L3), name, geometry, SRID, created_at_column (required), updated_at_column (optional), where_clause. On AOI: territory_level_3_column, additional_columns. Generic layers in etl.layers[] (ETL + map + downloads in one block) |
ETL plan, mapLayersConfig.json, and downloadThemesConfig.json. Extra layers: Generic layer migration |
| 3 β Application | Area-of-interest KPI label (area_of_interest), date and date-time formats |
Dashboard, listings, and detail |
| 4 β Interface | Hierarchy labels, screen titles, AOI detail panel fields, map.initialView (territorial_bbox / manual / planet), groups and styles for fixed map layers |
Frontend, layer selector, and styles published on GeoServer |
| 5 β KPIs | Card colors, AOI area unit, count and mapping of theme KPIs (0β4) | kpis block in application.yaml, THEME_* cards, and kpi-job |
| 6 β About (optional) | Enable About page, banner title, tabs (label + .md / .markdown file; wizard can copy from any folder to config/about/) |
about-config.json + content in config/about/ |
In the terminal the wizard shows 5 numbered stages (1β5) plus the optional About block at the end β in documentation, About counts as stage 6.
Object storage (SeaweedFS) is not asked in the wizard. Credentials and endpoint live in environment.object_storage in adopter-config.yaml (see adopter-config.yaml.example); ./config.sh copies this to DSP_OBJECT_STORAGE_* in .env on reapply. The geo-file job schedule (DSP_GEO_FILE_GENERATION_CRON) comes from ./setup.sh, not this stage.
./config.sh (option 2 β edit) reopens the same 6-stage wizard with current values filled in.
Jobs generated automatically¶
The wizard does not ask which fixed jobs to enable. Generated application.yaml always turns on L1, L2, L3, area of interest, and kpi-job. The layer-jobs flag becomes true when etl.layers[] has entries; otherwise false.
The area shown on KPIs does not come from the source: kpiCalculationJob computes dsp.area_of_interest.area and writes themes to dsp.kpi_measure after migration. Stage 5 sets theme_count (0β4), each theme layer, the area unit, and card colors. Detail: KPI calculation.
Advanced SQL in source_table (fixed levels)¶
In the wizard, source_table must be schema.table. For SQL subqueries (JOINs, aliases), edit adopter-config.yaml directly with a folded YAML block (>-), as in adopter-config.yaml.example. Column names below must match SELECT aliases. Then ./config.sh β Reapply.
Unified generic layers (etl.layers[])¶
Each item feeds three destinations from a single configuration:
Field in adopter-config.yaml |
Where used |
|---|---|
source_table, role columns, where_clause, srid |
application.yaml β migration job |
layer_name, display_name, group_key, active_default, color, fill_color |
mapLayersConfig.json β map and GeoServer |
(derived from layer_name + display_name) |
downloadThemesConfig.json β Downloads screen (strategy: aoi_linked) |
The same source_table may appear more than once if layer_name (and thus destination table dsp.<name>) differs. When adding another layer with the same source in the wizard, structure fields (PK, FK, geometry, dates, SRID) are reused as defaults.
To not migrate or display a layer, remove it from etl.layers[] and reapply β do not use enabled: false in adopter YAML.
In stage 6/6, the adopter can decline the About page; then it stays disabled. If enabled, the wizard validates Markdown extension and file existence and generates tab ids (tab-1, tab-2, β¦) in about-config.json.
Protected contract
The generated file contains only adopter-editable fields. Internal DSP contract keys (WMS layer IDs, target table names, KPI codes) stay fixed in core templates and are not exposed in the wizard.
About page (config/about/)¶
The config/about/ folder holds sample About page content for the frontend: about-config.json.example (sample index) and demo Markdown (*.quickstart.md.example, copied in Demonstration mode). The normal wizard flow is to point to a Markdown file anywhere on the machine, which is copied to config/about/.
Adopter YAML (config/adopter/adopter-config.yaml / .yaml.example) has an about section with fields:
| Field | Purpose |
|---|---|
enabled |
Enables/disables custom About page |
banner_title |
Title shown on the page banner |
tabs |
List of {label, file} β in the wizard, file can be from any folder (copied to config/about/); on manual YAML edit, only the filename already in config/about/ (absolute paths or paths outside the folder are rejected on apply) |
apply_config() generates config/about/about-config.json with automatic ids (tab-1, tab-2, β¦) from tab order. The backend reads these files at /config/about/ inside the image (copied at build). Variables: DSP_ABOUT_CONFIG_FILE and DSP_ABOUT_CONTENT_DIR (.env.example).
./setup.sh¶
Prepares databases, GeoServer, and (real flow) the first migration. Runs with no arguments and shows a menu with three options:
| Option | Action |
|---|---|
| 1 β Demonstration | Synthetic Brazil seed, no JDBC source or migration job. For exploring the UI or evaluating the stack. |
| 2 β Real adopter (ETL via JDBC) | Requires ./config.sh first. Asks when and how migration should run (submenu below). Writes to dsp-db + dsp-geoserver-db. |
| 3 β Status / cleanup / exit | Container status and URLs; optionally removes Docker resources for the project. Does not start or migrate. |
Option 2 submenu β migration plan¶
1. When should the initial load run?
| Choice | Effect |
|---|---|
| Run now | First migration during this ./setup.sh |
| Schedule for later | Databases stay empty on setup; first load at DSP_MIGRATION_SCHEDULED_AT |
2. How should migration behave afterward?
| Choice | Run now | Schedule for later |
|---|---|---|
| One-time | once: migrates on setup and stops the job container |
scheduled-once: waits for date/time, migrates once, publishes GeoServers, and exits |
| Continuous (periodic re-sync) | continuous: migrates on setup and keeps the container with supercronic on DSP_MIGRATION_CRON |
continuous + DSP_MIGRATION_SCHEDULED_AT: first load at chosen time, then cron |
In Continuous mode, the script asks How often should the data be synchronized after the initial migration?: every day at a time; every N hours; or every N minutes (1β59, useful for local testing). The Schedule for later time is reused if the choice is βevery dayβ.
Next (still setup step 3, option 2), it asks for the download file pre-generation cron β 5-field Unix expression for DSP_GEO_FILE_GENERATION_CRON (default: current .env value or 0 2 * * *). It should sit in a window after periodic migration when DSP_MIGRATION_CRON exists. The adopter does not type the migration expression manually; for pre-generation, enter the cron directly or accept the default with Enter.
Migration and pre-generation values are written to .env at the end of setup (step 9), along with DSP_MIGRATION_*. Re-running ./setup.sh (real adopter) repeats the questions, using .env as default.
Migration timezone: DSP_MIGRATION_TZ in .env (see .env.example). Pre-generation inherits DSP_MIGRATION_TZ when DSP_GEO_FILE_GENERATION_TZ is empty.
| Combination | DSP_MIGRATION_EXECUTION_MODE |
Job container after setup |
|---|---|---|
| Run now + One-time | once |
Stopped |
| Schedule + One-time | scheduled-once |
Active until load; then exits |
| Run now + Continuous | continuous |
Active (supercronic) |
| Schedule + Continuous | continuous + DSP_MIGRATION_SCHEDULED_AT |
Active (wait, then supercronic) |
If config/Job-Data-Migration/application/application.yaml is still identical to the template (.example), setup.sh stops with an error β run ./config.sh or edit the file first.
Databases and layer publication¶
Before migrating, the script waits for PostgreSQL full initialization (pg_isready + dsp and data_migration schemas created by init SQL).
| Initial load | GeoServers at end of setup |
|---|---|
| Run now (migration on setup) | populate_geoserver.sh publishes layers on both GeoServers (REST) |
| Schedule for later | GeoServers start without layers; publish_geoservers.sh in the job entrypoint runs after the first scheduled load succeeds |
AOI and generic layers exist on GeoServers only after the job populated geoserver-db.
./start.sh¶
Use after ./setup.sh. It does not run migration, seed, GeoServer populate, or start databases/jobs β it only rebuilds and starts dsp-backend, dsp-frontend, and dsp-gateway (with docker compose β¦ --no-deps, so infra is not restarted via depends_on).
| Script step | What it does |
|---|---|
| 1 β Prerequisites | Docker; create/validate .env |
| 2 β Repositories | Looks for backend and frontend in the short folder (../backend, ../frontend) and then in ../dsp-backend and ../dsp-frontend. Writes the path it used to DSP_BACKEND_PATH / DSP_FRONTEND_PATH. |
| 3 β Config on disk | Requires installation-config.json, mapLayersConfig.json, and downloadThemesConfig.json (valid; typically from ./config.sh or demo quickstart) |
| 4 β Infrastructure | Only verifies expected containers are running (fails with hint if not). Demo: two databases + two GeoServers. Real adopter: same core +, per .env, migration job, dsp-object-storage, and geo-file job |
| 5 β Site URL | Shows public frontend URL (dsp_public_base_url + VITE_BASE_URL) |
| 6 β Application | Build/up of backend, frontend, and gateway |
| At end | Stack summary, URLs, and tips (including geo-file when applicable) |
Map layers and first data load remain ./setup.sh responsibility (or the scheduled job afterward). If you ran docker compose down without -v, ./start.sh does not restart databases or GeoServers β follow the command the script prints on error or run ./setup.sh again.
Databases and GeoServer¶
Only databases publish a port on the host. HTTP services are reachable only through the gateway. The two jobs do not expose HTTP β they start via Compose profile.
| Service | Access | Role |
|---|---|---|
| dsp-db | port 20654 | Operational database β business + bbox/centroid. Spring Batch metadata: schema data_migration (migration job) and schema geo_file_generation (geo-file job) |
| GeoServer DB (dsp-geoserver-db) | port 20656 | Full geometry dsp.* |
Migration job (dsp-job-migration) |
profile migration, no HTTP port |
ETL from JDBC source to dsp-db and geoserver-db |
Object storage (dsp-object-storage) |
profile object-storage, host port 8333 (optional) |
SeaweedFS (weed mini) β DSP S3 API. Required for real adopter |
Geo-file job (dsp-job-geo-file-generation) |
profile object-storage, no HTTP port |
Pre-generates download CSV in SeaweedFS. Required for real adopter |
| GeoServer Exhibition | via gateway, /geoserver-exhibition/ |
Map WMS/WFS from geoserver-db |
| GeoServer Download | via gateway, /geoserver-download/ |
Download WFS (consumed by backend) |
Dual-write flow¶
flowchart LR
src[(Adopter JDBC source)]
job[Migration job]
job -->|"business + bbox/centroid"| dspdb[(dsp-db)]
job -->|"full geometry"| exdb[(dsp-geoserver-db)]
exdb --> gsEx[GeoServer Exhibition WMS]
exdb --> gsDl[GeoServer Download WFS]
exdb --> geoFile[Geo-file job]
geoFile -->|pre-generated CSV| s3[(dsp-object-storage SeaweedFS)]
dspdb --> be[Backend serves API]
be --> fe[Frontend consumes API + WMS]
be -->|WFS downloads| gsDl
be -->|CSV S3-first| s3
Gateway (dsp-gateway)¶
nginx container that is the single entry point for the stack. Frontend, backend, and both GeoServers do not
publish a port on the host β everything enters through DSP_GATEWAY_HOST_PORT (default 8026).
Configuration lives in config/Gateway/nginx/default.conf.template, is copied into the image on docker compose build (/etc/nginx/templates/) and processed by envsubst when the container starts, substituting only DSP_* variables. Volume dsp_gateway_cache holds cache only, not templates.
| External route | Internal target |
|---|---|
/ |
redirects to /dsp/ |
/dsp/ |
dsp-frontend:8080 |
/dsp-backend/ |
dsp-backend:8080 (follows DSP_BACKEND_CONTEXT_PATH) |
/geoserver-exhibition/ |
dsp-geoserver-exhibition:8080/geoserver/ |
/geoserver-download/ |
dsp-geoserver-download:8080/geoserver/ |
/gateway/health |
local nginx response |
Both GeoServers respond at /geoserver internally, so each gets its own external
prefix and a rewrite. Each also gets PROXY_BASE_URL with its public URL so
GetCapabilities and web UI links are correct.
Because the gateway resolves upstreams at runtime via Docker DNS, it starts even if some service
is down β it returns 502 instead of failing on boot. That enables demo mode in
./setup.sh, which does not start backend or frontend.
Cache¶
Cache is configured but off by default. It covers only WMS/WFS endpoints
(/geoserver-exhibition/<workspace>/wms, /wfs and equivalents under /geoserver-download/), which have
no session β GeoServer web UI and REST are excluded so admin login is not broken.
To enable, set DSP_GATEWAY_CACHE_BYPASS empty in .env and recreate the container:
# .env
DSP_GATEWAY_CACHE_BYPASS=
DSP_GATEWAY_CACHE_TTL=10m
docker compose --env-file .env up -d --force-recreate dsp-gateway
Header X-Cache-Status (HIT, MISS, BYPASS) is on every GeoServer response for
checking behavior. To clear cache, remove volume dsp_gateway_cache.
Default URLs¶
| Service | URL |
|---|---|
| Frontend | http://localhost:8026/dsp/ |
| Backend API | http://localhost:8026/dsp-backend |
| GeoServer Exhibition | http://localhost:8026/geoserver-exhibition/web/ |
| GeoServer Download | http://localhost:8026/geoserver-download/web/ |
| Gateway health | http://localhost:8026/gateway/health |
Relevant environment variables (core .env)¶
.env is created automatically on first run of ./config.sh, ./setup.sh, or ./start.sh (from .env.example). You do not need to copy it manually.
./config.sh writes JDBC, SRID, and DSP_OBJECT_STORAGE_* (from environment in adopter YAML) to .env. Migration and geo-file schedules come from ./setup.sh (real adopter). Understanding the variables below helps customize the installation and troubleshoot.
| Variable | Purpose |
|---|---|
DSP_SOURCE_JDBC_URL |
Adopter data source JDBC URL (database to migrate) |
DSP_SOURCE_DB_USER / DSP_SOURCE_DB_PASSWORD |
JDBC source credentials |
DSP_MIGRATION_EXECUTION_MODE |
once: load on setup and stop job. continuous: supercronic on DSP_MIGRATION_CRON. scheduled-once: one load at DSP_MIGRATION_SCHEDULED_AT |
DSP_MIGRATION_CRON |
5-field Unix cron generated by setup (e.g. 0 */6 * * * or */2 * * * *). Only continuous |
DSP_MIGRATION_SCHEDULED_AT |
Date/time of first load when setup chooses Schedule for later |
DSP_MIGRATION_TZ |
IANA timezone for the clock (.env.example) |
DSP_GEO_FILE_GENERATION_EXECUTION_MODE |
continuous (default): supercronic on DSP_GEO_FILE_GENERATION_CRON. once: one run via compose run |
DSP_GEO_FILE_GENERATION_CRON |
5-field Unix cron for geo-file job (e.g. 0 2 * * *). Set in ./setup.sh (real adopter), not in ./config.sh |
DSP_GEO_FILE_GENERATION_TZ |
Pre-generation timezone; empty inherits DSP_MIGRATION_TZ |
| Core 2-database credentials | User/password for dsp-db and dsp-geoserver-db |
DSP_GEOSERVER_WFS_BASE_URL |
GeoServer Download WFS URL on Docker network (backend β download) |
DSP_PUBLIC_BASE_URL |
Public stack URL (http://localhost:8026). Feeds WMS/WFS URLs from ./config.sh and GeoServersβ PROXY_BASE_URL |
DSP_GATEWAY_HOST_PORT |
Gateway HTTP port (default 8026) |
DSP_GATEWAY_CACHE_BYPASS / DSP_GATEWAY_CACHE_TTL |
Enable/disable nginx cache and TTL |
DSP_CORS_ALLOWED_ORIGINS |
Allowed origins for backend CORS |
DSP_ABOUT_CONFIG_FILE / DSP_ABOUT_CONTENT_DIR |
About index and Markdown folder (default file:/config/about/β¦) |
DSP_OBJECT_STORAGE_ENDPOINT |
When set, enables geo-file job (profile=geo-file) |
| Frontend build args | VITE_BASE_URL, VITE_DSP_API_URL β base path and API URL used in image build |
DSP_OBJECT_STORAGE_* / DSP_OBJECT_STORAGE_HOST_PORT |
SeaweedFS internal endpoint (http://dsp-object-storage:8333), bucket, credentials, and host port for diagnostics β derived from environment.object_storage on ./config.sh reapply (not asked in wizard). Brazil Demo leaves endpoint empty |
DSP_BACKEND_PATH / DSP_FRONTEND_PATH / DSP_JOB_MIGRATION_PATH / DSP_JOB_GEO_FILE_GENERATION_PATH |
Path to each module's code for the build. The script looks in the short folder and then in the dsp-* folder, and writes the folder it used here. When both are missing, the download creates the dsp-* folder. |
See also: Full installation, Databases.
Generated configuration structure¶
config/adopter/adopter-config.yaml is the central file produced by the ./config.sh wizard. From it are derived operational files consumed by the backend (installationConfig.json, mapLayersConfig.json, downloadThemesConfig.json, about-config.json) and the migration job (application.yaml), so each module does not need isolated manual configuration.
The ./config.sh wizard produces a single adopter-config.yaml and derives all operational artifacts consumed by other modules. downloadThemesConfig.json enters this pipeline as the theme catalog for the Downloads screen and backend WFS proxy. about-config.json enters as the content index (banner + tabs) for the frontend About page.
flowchart LR
configSh["./config.sh"]
configSh --> installJson["installation-config.json"]
configSh --> mapJson["mapLayersConfig.json"]
configSh --> downloadJson["downloadThemesConfig.json"]
configSh --> aboutJson["about-config.json"]
configSh --> appYaml["Job-Data-Migration application.yaml"]
configSh --> geoYaml["Job-Geo-File-Generation application.yaml"]
configSh --> dotenvStorage[".env DSP_OBJECT_STORAGE_* / DSP_SOURCE_*"]
installJson --> build["docker compose build<br/>dsp_config context"]
mapJson --> build
downloadJson --> build
aboutJson --> build
appYaml --> build
geoYaml --> build
dotenvStorage --> setupSh["./setup.sh writes crons to .env"]
build --> backendImg["dsp-backend /config"]
build --> geoserverImg["GeoServers /config"]
build --> jobImg["dsp-job-migration /config"]
./config.shβ adopter entry point; wizard, reapply, or recreateadopter-config.yamland generate operational JSON/YAML listed above.select-runtime-config.shβ on Docker build, picks active file or.exampleand copies to/configinside the image.installation-config.jsonβ labels, hierarchy, screens, KPIs, andscreens.home.detail.fields(DSP_INSTALLATION_CONFIG_FILE). That array does not go to the jobapplication.yaml.mapLayersConfig.jsonβ WMS groups and layers; published on GeoServers bypopulate_geoserver.sh.downloadThemesConfig.jsonβ download themes (AOI +etl.layers[]);formatsiscsvandgpkg;wfsBaseUrlat${DSP_PUBLIC_BASE_URL}/geoserver-download/dsp/wfs.about-config.jsonβ About index (enabled,bannerTitle,tabswith idstab-1,tab-2, β¦).application.yamlβ ETL plan. Copied to job image at build (with entrypoint and GeoServer publish scripts).config/Job-Geo-File-Generation/application/application.yamlβ S3 for pre-generation job (no cron; schedule only in.envvia./setup.sh).- Images
dsp-backend, GeoServers,dsp-job-migration, databases, anddsp-gatewayβ configs and init SQL copied at build viadsp_config; volumes hold data only (and gateway cache). - Image
dsp-object-storageβ SeaweedFS (weed mini) with credentials ins3.jsonin the image. Volumedsp_object_storage_dataholds objects; internal volume size and count are computed byweed minifrom free space on the Docker volume.
See also: Data flow (runtime) and dsp-backend (download environment variables).