Deployment
Container images
Section titled “Container images”Each part of jtWMS ships as its own container image: the gateway, each service, the web app and this documentation site. Images are built and published per component, so one component can be released without rebuilding the others.
Alongside them, jtWMS needs:
| Dependency | Used for |
|---|---|
| A relational database | One separate database for each service. |
| A message broker | The event bus between services. |
| An in-memory store | Rate-limit counters, session revocation and short-lived caching of API key checks. |
| An email server | Password reset and invite emails. |
Services behind a gateway
Section titled “Services behind a gateway”The gateway is designed to be the only way in: the web app’s server passes its API calls on to it, and the services trust the identity information it forwards. The services don’t check that a request came from the gateway, so they, their databases, the message broker and the in-memory store must sit on a private network that clients can’t reach. The services keep no session state of their own: every request carries its own credentials, and data lives in the service’s database, the message broker or the in-memory store. Events waiting to be published, and webhook deliveries waiting to be sent or retried, are stored in the database and resume after a restart. There are three exceptions:
- Token signing keys. The identity service signs and encrypts tokens with certificates supplied as files. Sessions survive a restart, and every instance issues and accepts the same tokens. Only in development mode does it generate temporary keys instead, so restarting it there ends every session.
- Webhook secret keys. The webhooks service keeps the keys that encrypt webhook signing secrets in files, which must be on persistent storage: without them the stored secrets can’t be decrypted.
- Password reset emails. A forgot-password email still waiting to be sent when the identity service stops is not sent, and the user can ask again.
- Invite emails. An invite email still waiting to be sent when the identity service stops is not sent. The invite itself is kept: an administrator can share the link shown when they created it, or invite again.
Its own database per service
Section titled “Its own database per service”Every service has its own database, and no service connects to another’s. A service’s schema changes ship with that service. The databases can share a database server or run on separate ones.
Health endpoints
Section titled “Health endpoints”The gateway and every service expose a /health endpoint for health checks. A
service that defines permissions reports itself unhealthy until it has registered them with the
identity service, so traffic can be held back until every permission exists. The development
stack starts the gateway only once the services behind it report healthy.
Configuration
Section titled “Configuration”Each image carries defaults, and settings are supplied through environment variables when the container starts: connection strings for the database, broker and in-memory store, the addresses the gateway forwards to, the keys services use to call each other, token lifetimes, the burst rate limit and webhook retry settings. Secrets are supplied this way rather than built into an image. The outbound service also needs a certificate, supplied the same way, to encrypt the keys that protect carriers’ API keys: outside development it won’t start without one.
Outside development mode, the identity service also needs:
- two certificates, one to sign tokens and one to encrypt them;
- the fixed URL to name as every token’s issuer.
Rotating a certificate means adding the new one alongside the old, so sessions carry on. Outside development, schema migrations run at startup only when a setting asks for it, so a deployment can back up first. The first platform administrator is created from settings, and only while there is none. No default credential exists outside development mode.
The development stack
Section titled “The development stack”The repository’s container stack is a development stack, not a production deployment, and it isn’t meant to be copied as is:
- It publishes every service, the databases, the broker and the in-memory store on the host, for debugging, so the private-network rule above doesn’t hold for it.
- It runs every service in development mode. Each service applies its own schema migrations when it starts, and the identity service generates temporary signing keys. Outside development mode, migrations run at startup only when configured to, and the identity service needs its certificates and a fixed token issuer (see Configuration).
- Development mode relaxes some protections described in the security model: webhook targets may use HTTP and private or loopback addresses, and error responses include internal exception details.
- Development mode also creates default credentials: a platform super-admin account, which isn’t subject to the password policy or to the rule that you can only grant permissions you hold, and a machine client with a fixed secret. Remove or change them before using the stack anywhere but a local machine.
A production deployment
Section titled “A production deployment”The hosted environment runs the same images as the development stack, with a production layer on top:
- One way in. Only a TLS-terminating edge is published. The gateway, the services, the databases, the broker and the in-memory store sit on a private network.
- Production mode. Every service runs in production mode, so the development defaults and relaxations above are off.
- Pinned releases. A release is the set of image digests it runs, so a release can be started again exactly, and that is how a rollback works.
- A backup before every release. Each release starts only after a full database backup has been taken and checked. Each service then applies its own schema migrations as it starts.
- Automatic rollback. If any service doesn’t report healthy, the previous release is started again.
- Nightly backups, proved restorable. A backup is taken every night and restored into a throwaway database that same night to prove it is usable. It is also copied to a second machine. A restore loads into new storage and keeps the data it replaces.