Architecture¶
Strawly is built as a set of independently deployable Docker services orchestrated by Docker Compose. This page describes how the pieces fit together.
Services¶
Backend¶
- Language: TypeScript / Node.js
- Framework: Express.js
- Port: 3001 (default)
- Purpose: Central REST API. Handles authentication (JWT), user management, and aggregates data from all modules for the frontend.
- Database: PostgreSQL via Prisma ORM
- Schema ownership: The backend is the sole owner of the database schema. It runs all migrations. Modules never run migrations.
Frontend¶
- Language: TypeScript / Node.js
- Framework: Next.js
- Port: 3000 (default)
- Purpose: Web UI. Communicates exclusively with the backend API — it does not talk to modules or the database directly.
PostgreSQL¶
- Port: 5432 (internal only — not exposed to the host by default)
- Purpose: Shared relational database for all services
- Data separation: Each module writes to its own prefixed tables (
az_*,aws_*,gcp_*)
Migrations (one-shot container)¶
- Runs
prisma migrate deployagainst the database on startup - Exits after completion — it is not a long-running service
- Starts before the backend to ensure schema is up to date
Seeder (one-shot container)¶
- Seeds initial data (system roles and the default admin account from
ADMIN_EMAIL/ADMIN_PASSWORD) - Runs after migrations and exits
- Safe to run on an already-seeded database (idempotent)
The module registry is seeded separately by the seed-modules script, not by this container. See Module registry below.
Modules (optional)¶
Each module is an independent service. See Modules Overview for the current list.
Data flow¶
User → Frontend (Next.js :3000)
↓ REST API calls
Backend (Express :3001)
↓ Prisma ORM
PostgreSQL (:5432)
↑ Prisma ORM (read/write to prefixed tables)
Module (e.g. :3002)
↑ Azure / AWS APIs (external)
The frontend never communicates with modules directly. The backend queries the shared database where modules have written their findings.
Startup order¶
The generated compose starts services in dependency order:
- postgres — database
- migrations — runs
prisma migrate deploy, waits for postgres to be healthy, then exits - seeder — runs
prisma/seed.ts, waits for migrations to complete, then exits - backend — API, waits for the seeder to complete successfully
- modules — optional services, wait for the backend to be healthy, start in parallel
- frontend — UI, waits for the backend to be healthy
Health checks and service_completed_successfully conditions gate each tier from starting.
Module registry¶
The module registry lives in the backend's settings table as global rows keyed module:<provider>:<...>. It is populated by the seed-modules script (npm run seed-modules), which reads strawly-deployment.yml and upserts one settings entry per module with its display name, icon, service URL, schedule, and enabled flag. The frontend reads this registry to know which module sections to show. Disabling a module in the YAML and re-running the seeder flips its entry to disabled and the UI hides its section.
Repository structure¶
Each service lives in its own git repository and is built into its own Docker image:
| Repository | Image | Description |
|---|---|---|
Strawly/backend |
git.strawly.app/Strawly/strawly-backend |
Express.js API |
Strawly/frontend |
git.strawly.app/Strawly/strawly-frontend |
Next.js UI |
Strawly/optimizations-azure |
git.strawly.app/Strawly/optimizations-azure |
Azure module |
Strawly/deployment |
— | Orchestration scripts and config |
Override the full image reference per service with BACKEND_IMAGE, FRONTEND_IMAGE, or a module's image field in strawly-deployment.yml. Images are built by CI on every push to main and on version tags.
Secrets and configuration¶
All sensitive configuration is passed via environment variables at runtime (never baked into images). The deployment repository provides:
.env.example— template with all required variablesgenerate-secrets.sh— prints cryptographically random values forJWT_SECRET,CREDENTIALS_ENCRYPTION_KEY, andPOSTGRES_PASSWORDto paste into.env