For AI agents: a documentation index is available at /llms.txt. A markdown version of this page is available at /guide/self-hosted/get-started/architecture.md.
Architecture
The services, volumes, ports and data stores that make up a deployed Enterprise instance.
Knowing what runs where is what makes backup, migration and troubleshooting straightforward rather than guesswork.
#The container stack
A Docker deployment defines four services:
| Service | Purpose | Port |
|---|---|---|
orangescrum-app | The application | 8080 |
orangescrum-postgresdb | Main PostgreSQL database | 5432 (internal) |
orangescrum-reports | Advanced Dashboard (Superset) | 8088 |
orangescrum-reports-db | Superset's own PostgreSQL database | internal |
Two databases, not one
Reporting runs on Superset, which keeps its own metadata database separate from the application's. Both must be backed up โ a backup covering only the main database restores an instance with no dashboards.
The reports service depends on its database being healthy before it starts, so on a cold boot expect it to come up last. Allow a minute or two before concluding something is broken.
#Persistent volumes
Four named volumes hold everything that must survive a container rebuild:
| Volume | Mounted at | Contents |
|---|---|---|
orangescrum-app-files | /var/www/html/webroot/files | Uploaded files and attachments |
orangescrum-app-config | /var/www/html/config | Application configuration |
orangescrum-postgres-data | /var/lib/postgresql/data | Main application database |
orangescrum-reports-data | /var/lib/postgresql/data | Superset reports database |
Volume names are prefixed by the compose project
On disk they appear as orangescrum-ee_orangescrum-app-files and so on. Always
confirm with docker volume ls before writing backup or restore commands โ
guessing the prefix is the most common cause of a restore that silently
populates nothing.
#Data outside Docker
Not everything lives in a volume:
| Item | Typical location | Why it matters |
|---|---|---|
| Application data root | /data (or your configured path) | Application files on the VM host |
| Host config | /etc/app/ or the app root | Environment-specific settings |
| Cron jobs and scripts | /etc/cron.d/ or the app folder | Scheduled digests and reminders |
| Compose definition | docker-compose.yml | The stack itself |
| Reports image build | osreports.Dockerfile | Custom Superset build |
| Secrets | ./secrets/ | Plaintext service passwords |
#The secrets directory
Four files hold credentials the services read at startup:
| File | Used by |
|---|---|
secrets/postgres_password.txt | Main database |
secrets/reports_admin_user.txt | Reports service |
secrets/reports_admin_email.txt | Reports service |
secrets/reports_admin_password.txt | Reports service |
These are plaintext credentials
Anything that backs up the secrets directory produces an archive containing live passwords. Store those archives encrypted, restrict who can read them, and never put the directory in version control.
#Traditional deployment
Without containers the shape is the same, just managed by the host:
- PHP-FPM behind Apache or Nginx, document root at
webroot/ - PostgreSQL as a system service
- Attachments under
webroot/files, configuration inconfig/ - Cron entries for scheduled jobs
Everything above the document root must be unreachable over HTTP.
#Background jobs
Some behaviour depends on scheduled work rather than the request cycle:
The record is written immediately; the email fires from a background job.
Stored against the task, delivered by a scheduled job at the chosen time.
If digests never arrive, check the scheduler first
The data being present in the database does not mean delivery ran. Confirm the cron entries survived your install or migration โ they live outside Docker and are easy to lose.
#Request path
Browser
โ Load balancer / reverse proxy (TLS terminates here)
โ orangescrum-app :8080
โ orangescrum-postgresdb :5432
โ orangescrum-reports :8088 (Advanced Dashboard)
โ orangescrum-reports-db
TLS terminates at the proxy; the application itself listens on plain HTTP inside
the network. That is why 443 is what you expose and 8080 is what you do not.