Skip to content
Last updated: Sep 25, 2026

Environments ​

One page per environment: where it is, how to get in, what runs there, and who to ask. If you are new and just want access to staging, read Staging and stop there.

EnvironmentStatusFront doorPlatform
LocalPer-developerhttp://localhost:5173Docker Compose
StagingLive — the shared environmenthttps://letsgo.finaisse.com (Demo Tenant App)Railway
ProductionNot serving traffic—AWS (cost-parked)

Where credentials actually come from

This site is gated by Cloudflare Access but is still documentation, not a secret store — no passwords, keys or connection strings are written on this page, and none should be added to it. Every "how to get it" below points at a system that holds the real value.

Staging (Railway) ​

The shared environment. Deployed from GHCR images built in blitz / blitz-ui; the IaC lives in fin-infra/railway/.

URLs ​

WhatURLWho can reach it
Demo Tenant Apphttps://letsgo.finaisse.comAnyone with a tenant login
Tenant app (per-tenant)https://<tenant>-app.finaisse.com — e.g. test-app.finaisse.comTenant users; one DNS record per tenant on staging. Per-tenant detail: Tenants on staging
Management portalhttps://mgmt.finaisse.comAdmins only, via Cloudflare Access SSO
Temporal Web UItemporal-ui-stage.up.railway.app⚠️ currently the public internet — see the warning below
Railway dashboardhttps://railway.appAnyone on the Railway project
GHCR imageshttps://github.com/orgs/finaisse-org/packagesfinaisse-org members

Backends (blitz-api, ws, wfw, blitz-apimgmt, …) have no public URL by design. They are private on *.railway.internal; the frontend nginx containers proxy /api to them. A browser — or your laptop — cannot reach them directly, and that is the intended posture, not a gap.

Temporal UI is publicly reachable — unauthenticated

temporal-ui-stage.up.railway.app is a Railway-generated public domain with no authentication in front of it. Verified 2026-08-20: an anonymous request to /api/v1/namespaces returns namespace metadata, and the workflow-search endpoint answers HTTP 200.

Anyone who knows or guesses the hostname can browse staging workflow executions — which carry tenant identifiers and financial workflow arguments — and the Temporal UI's write paths (terminate, signal) sit behind the same absent auth.

The fix is to remove the public domain (Temporal UI is only needed by operators, who can reach it in-network) or to put it behind the same Cloudflare Access policy as mgmt.finaisse.com. Do not treat this as documented-and-therefore-fine. Tracked below in Known gaps.

Getting access ​

Access is per-system — there is no single staging login.

To get…You needHow
Tenant app loginA user in the tenant DBAsk an admin to create one, or use the mgmt portal
Management portalYour identity in the admins-only Cloudflare Access policyAsk a Cloudflare Zero Trust admin to add you; WARP-enrolled laptops then get seamless SSO
Railway dashboardMembership of the Railway projectAsk a Railway project owner to invite you
Running fin-infra scripts locallyRAILWAY_API_TOKEN, RAILWAY_PROJECT_ID, RAILWAY_ENVIRONMENT_IDMint the token from your own Railway account; the two IDs come from the project URL
GHCR image pullsfinaisse-org membership + a PAT with read:packagesSee Build

Once you have the Railway variables, put them in fin-infra/railway/.env (gitignored) and use the wrapper, which loads them for you:

bash
cd fin-infra/railway
./rw.sh status              # deployment status of every service
./rw.sh logs blitz-api --limit 50

rw.sh is read-only by design. Deploy/scale/provision must be run explicitly — see Run & Operate.

Service credentials ​

No credential values live in this repo or on this site. They are held as Railway service variables and are readable in the Railway dashboard by anyone on the project.

CredentialUsed forWhere it is set
RUSTFS_ACCESS_KEY / RUSTFS_SECRET_KEYRustFS/S3 servers3 service
BLOBACCESSKEY / BLOBSECRETKEYApp services' S3 access — must equal the two above, or bucket creation fails on bootBackend services
MGMT_API_KEYGuards apimgmt /api/o/* — same value on both blitz-apimgmt and blitz-mgmtuiBoth services
JWTSECRETJWT signingBackend services
DATABASE_URL / POSTGRES_*PostgresRailway-managed Postgres plugin
GHCR_USERNAME / GHCR_TOKENPulling private imagesAll services with a GHCR image

railway/environments/staging/variables.env.example in fin-infra is the authoritative list of what must be set. These are not merely documentation: the provision and vars:sync scripts read them from process.env, and anything unset is silently omitted, deploying a misconfigured service. Export them before running either script.

No documented secret-distribution channel

There is currently no agreed place from which a new joiner obtains these values — the Railway dashboard is the de-facto source, which means anyone who needs one value gets all of them. Adopting a secrets manager is an open decision, not a settled practice; do not invent a channel.

There are no SAP secrets in the IaC — bapiproxy resolves SAP credentials per-tenant at runtime from the mgmt DB.

Tenants on staging ​

One entry per tenant: URL, database, storage buckets, users, and what that tenant is for. For how tenancy itself works (routing, adding/deleting a tenant, the tenant_base template), see Multi-Tenancy Model.

TenantURLDatabasePurposeStatus
defaulthttps://letsgo.finaisse.comfinanceThe original / demo tenantEnabled
Finaisse Testhttps://test-app.finaisse.comfintestTesting tenantEnabled

Both are on staging; there is no production tenant.

What is on this page vs. Multi-Tenancy Model

This section answers "what is this tenant, where is its data, who is in it". Multi-Tenancy Model answers "how does tenancy work at all". Credentials live in neither — both point at where the real values are held.

default ​

The original tenant, and the one you get at the bare staging URL. Used for demos, so treat its data as something other people may be looking at. Verified against live staging 2026-08-24.

Display namedefault
URLhttps://letsgo.finaisse.com
Databasefinance
Primary userroot
EnabledYes
Created2025-12-01

default predates per-tenant provisioning, so it does not follow the pattern other tenants do:

defaultProvisioned tenants
Databasefinance — the original DBCloned from tenant_base
BucketsLiterally default / tempUnguessable UUIDs
OriginExisted before the tenancy modelCreated via the mgmt portal

Its buckets being the well-known names default and temp matters operationally: those two self-heal on RustFS boot, whereas per-tenant UUID buckets do not. That is why letsgo keeps working after a RustFS wipe while other tenants throw NoSuchBucket.

Database: finance, ~60 MB, 367 tables, 21 users (roles: administrator, agent, service; 2 permission rows), 4 connectors. finance is also the source for the tenant_base golden template every new tenant is cloned from — test data written here can end up in every future tenant, and restoring finance makes tenant_base stale until rebuilt (current as of 2026-08-24).

tenant_base is current (verified 2026-08-24)

The template matches finance exactly — a column-level comparison of all 4953 columns across 367 tables in both databases produces an identical checksum and an empty diff.

Do not date a template by file mtime. pg_stat_file('base/<oid>/PG_VERSION') is the obvious-looking check and it is wrong — PG_VERSION is written once when the database directory is created and never rewritten, so it dates the directory, not the contents. It reported this template as three weeks stale when it was in fact restored that morning. Compare information_schema.columns between template and source instead.

RBAC here is as thin as elsewhere — 2 permission rows — so default is not a realistic test of restricted roles either.

Finaisse Test (test-app) ​

The testing tenant on staging. Use this one for anything you would not want to do to default — trial runs, connector work, load and workflow testing. Verified against live staging 2026-08-24; answers HTTP 200 end-to-end, not merely a row in a table.

Display nameFinaisse Test
URLhttps://test-app.finaisse.com
Databasefintest
Primary userroot
EnabledYes (isenabled = true)
Created2026-08-06

test-app.finaisse.com is a per-tenant DNS record in Cloudflare, resolving to the blitz-ui Railway service. Routing matches the full FQDN against tm.tenant.domainname, so reaching the same UI on letsgo.finaisse.com gives you the default tenant instead — the app is host-agnostic and the tenant comes from the host.

Database: fintest, ~58 MB, 367 tables, on the same Railway Postgres server as mgmt, finance. Schemas: agent, cash, closehub, coresystem, demo, finbase, invoice, journal, public, reconciliation, sys. The connection string lives in tm.tenant.connectionstring in mgmt — stored in plaintext today, a known issue, not a convention to copy.

There is no separate credential for fintest — the same Postgres user that reaches mgmt reaches it (get it from the Railway dashboard, never from this page). Internally, services reach it at postgres.railway.internal:5432, which does not resolve from a laptop; for external access use the Railway TCP proxy for the Postgres service (Settings → Networking, a *.proxy.rlwy.net host and port):

bash
psql -h <tcp-proxy-host> -p <tcp-proxy-port> -U <user> -d fintest

This is a real database on a shared server

fintest is "test" by convention only — it sits on the same Postgres instance as finance and mgmt. A careless DROP or a mistyped -d reaches production-adjacent data. Confirm the -d before running anything destructive.

Object storage: two buckets, named as server-generated UUIDs so they cannot be guessed from the tenant name:

RoleBucketNotes
Default (dbucket)fd1742fe-cfcf-48a8-90a1-7e13383c51a9Persistent tenant objects
Temp (tbucket)5c52fb93-0cdc-4750-b347-e3c2686f62db1-day object expiry

Application code refers to the logical names default / temp; the fs service maps those to the UUIDs above.

RustFS objects are not durable on staging

RustFS previously ran with no volume, so every redeploy wiped all buckets. default and temp self-heal on boot, but per-tenant UUID buckets do not — losing them gives NoSuchBucket for this tenant while letsgo keeps working. If uploads fail here but work on letsgo, check the buckets exist before debugging anything else.

Users and roles: 21 users (sys.user), roles administrator/agent/service (sys.role), 2 permission rows (sys.permissions). Primary user is root; passwords are not recorded here — ask an admin, or reset via the management portal.

RBAC is close to inert

Only 2 permission rows and 2 role-permission mappings exist. With a near-empty permission catalogue, effectively only the root/administrator path passes checks, so this tenant is not a realistic test of role-based access. Do not conclude from "it worked in test-app" that a restricted role will work.

Connectors: sys.connector has 4 rows, sys.sftp 1, sys.secret 1. Any SFTP row points at an external counterparty endpoint; there is no Finaisse-run SFTP server on staging (the atmoz/sftp container is local-dev only). Credentials sit in sys.secret / sys.connectorcredential in this database.

What runs there ​

19 services. 12 backend microservices, 2 frontends, 4 infra, 1 managed Postgres.

TierServices
Frontend (public)blitz-ui, blitz-mgmtui
Backend (private)blitz-api :10001 · ws :10002 · blitz-apimgmt :9999 · agents :10013 · excelrw :10014 · recon :10015 · remotecontrol :10016 · bapiproxy :10017 · emailprocessor, wfw, wfwpdf, classicml (workers, no port)
InfraPostgres (managed) · RustFS/S3 · Valkey/Redis · temporal · temporal-ui

Two of these are .NET, not Bun — excelrw and bapiproxy. bapiproxy is additionally amd64-only. See Architecture.

blitz-apimgmt and bapiproxy have no /health route, so container-level success is the strongest deploy signal available for them.

Scaling ​

Staging scales itself down every night at 10:00 PM IST (cron: 30 16 * * * in scale-staging.yml). There is no scheduled scale-up — if you arrive to a dead environment, that is expected and you bring it back up yourself:

bash
bun run scale up all

Deploy and scale-up are the same Railway mutation, so a deploy also starts a stopped environment. A service showing SLEEPING in ./rw.sh status is scaled down, not broken.

Known gaps on this environment ​

  • Temporal UI is public and unauthenticated — see the warning above. Not previously recorded in Security findings; it needs a finding ID and an owner.
  • No secret-distribution channel — the Railway dashboard is all-or-nothing access.
  • Orphaned tenant data — deleting a tenant drops neither its database nor its buckets, and the bucket UUIDs are lost with the row. Capture them before deleting; see Multi-Tenancy Model.
  • Temporal is capped at 0.5 GB and is the most likely service to OOM under load — first knob to raise in LIMIT_OVERRIDES.

Local Development ​

Each developer runs the full stack via Docker Compose. Nothing here is shared, so there are no access requests to make — but note the two compose files (blitz and blitz-ui) and their profile divergence, covered in Local Development.

bash
# from blitz/src/
just up            # everything (default profile: all)
just up infra      # Postgres, RustFS/S3, Temporal, Temporal-UI, Valkey only
just down          # stop
just shutdown      # stop and remove containers

bun devapi         # API gateway, watch mode
bun devall         # all services, watch mode

# from blitz-ui/src/
bun run devui      # main UI → http://localhost:5173
Local endpointURL / port
Main UIhttp://localhost:5173
Mgmt UIhttp://localhost:5006
Temporal UIhttp://localhost:8080
SFTP test fixturelocalhost:2222

SFTP is local-only. atmoz/sftp in compose-3rdparty.yaml is a throwaway dev fixture standing in for a customer/bank SFTP so the connector path (SftpActivities / trySftp) can be exercised. Its credentials are hardcoded dev values in the compose file. There is no SFTP service on staging — in any real environment the SFTP endpoint belongs to the counterparty or a managed service. If you are looking for "the staging SFTP credentials", they do not exist.

Local .env files are shared out-of-band and drift between repos — a missing MinioOptions__* block is what makes excelrw crash-loop with exit 139. There is no local equivalent of Railway's missing-variable warning.

With MGMT_API_KEY unset locally, apimgmt's API-key guard is disabled — expected for local dev, and the reason a local mgmt UI works with no key.

Production (AWS) ​

Status: not serving traffic. There is no production URL, and no user-facing production environment to get access to today. Staging on Railway is the live environment.

The Terraform in fin-infra/aws/ is real, applied, and then cost-parked — roughly 3,600 lines of HCL across 80 .tf files, larger than the Railway IaC. Do not describe it as "not started".

Regionap-south-1 (Mumbai)
Remote stateS3 bucket finaisse-terraform-state, one key per root module
Appliedenvironments/preprod, global/{bootstrap,control-tower,iam}
RDSStopped, not destroyed — rds-autostop Lambda re-stops it every 12h, because AWS auto-restarts a stopped instance after 7 days

Applying preprod restarts real infrastructure

No resource is gated behind a count/enabled flag, so a plain terraform apply on preprod brings the parked infrastructure back up and starts incurring cost. Confirm intent before applying.

Detail lives in AWS architecture and the AWS implementation tracker; go-live security gates are in AWS production gate.

Provisioning a New Environment ​

Use this when standing up a new Railway environment (e.g. staging-2, qa).

Prerequisites: RAILWAY_API_TOKEN with access to the project; fin-infra cloned locally with Bun installed.

  1. Create the environment in the Railway dashboard → select the project → Environments → New Environment. Note the new RAILWAY_ENVIRONMENT_ID.
  2. Copy and fill the variable template:
    bash
    cp railway/environments/staging/variables.env.example railway/environments/<new-env>/.env
    # Edit the file and fill in real values
  3. Provision infrastructure:
    bash
    cd railway
    export RAILWAY_API_TOKEN=...
    export RAILWAY_PROJECT_ID=...
    export RAILWAY_ENVIRONMENT_ID=<new-env-id>
    
    bun run provision:dry    # Verify what will be created
    bun run provision        # Provision all infra + services
    This provisions Postgres, RustFS, Valkey, Temporal + Temporal-UI, and all 12 microservices + 2 frontends — see What runs there for the current service list. To provision in stages: bun run provision:infra (infra only) / bun run provision:services (microservices + frontend only).
  4. Initialise databases: bun run init-db — creates finance and mgmt, once per environment.
  5. Sync environment variables: bun run vars:sync — pushes shared/variables.ts to every backend service.
  6. Set secrets manually in the Railway dashboard — see the Service credentials table above for the current list (RUSTFS_ACCESS_KEY/RUSTFS_SECRET_KEY, BLOBACCESSKEY/ BLOBSECRETKEY, MGMT_API_KEY, JWTSECRET, DATABASE_URL/POSTGRES_*, GHCR_USERNAME/ GHCR_TOKEN). These are never stored in code.
  7. Deploy services: bun run deploy:all, then verify with bun run status — health checks should go green within a minute. Check logs if any service fails to start: bun run logs -- --service blitz-api.

Checklist:

  • [ ] Environment created in Railway dashboard, ID noted
  • [ ] Variable template copied and filled
  • [ ] Infrastructure provisioned (bun run provision:dry then bun run provision)
  • [ ] Databases initialised (bun run init-db)
  • [ ] Variables synced (bun run vars:sync)
  • [ ] Secrets set in Railway dashboard
  • [ ] Services deployed (bun run deploy:all)
  • [ ] All services healthy (bun run status)

See also ​

Finaisse Internal — Confidential. Access-restricted; not for external distribution.