Environments

1. Overview

EMS runs three environments on one Rancher-managed cluster, each in its own namespace, each against its own MySQL schema on the shared production MySQL cluster. "Production" is a posture as well as a place: stage deliberately runs the same Spring profile as production so that it exercises the production posture. See Spring Bootstrap and Configuration Precedence for what follows from that.

Never cache a deployed version number. Which image is live in a given namespace changes without notice. Read /management/info on the service itself, authenticated with the environment’s API key, and treat that as the only answer. A version written into a document or a note is wrong within days.

2. Service hostnames

Service Production Stage Development

admin-service

admin-service.idealogic.co.za
admin-service-v2.idealogic.co.za

admin-service-stage.idealogic.co.za

admin-service-dev.idealogic.co.za

Registration portal

registration.myriadevents.co.za

registration-portal-stage.idealogic.co.za

registration-portal-dev.idealogic.co.za

Membership portal

members.myriadevents.co.za

membership-ui-stage.idealogic.co.za

membership-ui-dev.idealogic.co.za

Admin portal

admin-portal-dev.idealogic.co.za

Mail sink

event-mail-stage.idealogic.co.za

event-mail-dev.idealogic.co.za

Two admin-service hostnames exist in production because the service has been running on two deployment lines during the migration to the current line. They serve different namespaces against the same schema, so a row in the database does not tell you which instance wrote it. They also form separate cache clusters, which means a direct SQL change requires a cache eviction against both — see Cache Management. The current state of that migration, and which host serves which tenant, is tracked in the design journal rather than here, because it is a transitional arrangement with an end date.

3. Namespaces and schemas

Environment Namespace Schema Notes

Production

ems-prod

wpca_prod

The current line. Owns the database migrations.

Production, legacy line

event-prod

wpca_prod

Frozen image, same schema. Retiring.

Stage

event-stage

event_admin_service_stage

Refreshed nightly from production at 04:00 SAST.

Development

event-dev

event_admin_service_dev

Loads fixture data under the faker and sample-data Liquibase contexts.

Do not query the event_admin_service schema. It is legacy and empty for current code — its result table is missing columns the running application uses, so queries against it return plausible but wrong answers rather than failing.

The Liquibase bookkeeping tables are renamed from their defaults in this estate. They are change_log_event_admin and change_lock_event_admin, not DATABASECHANGELOG and DATABASECHANGELOGLOCK. A migration investigation that greps for the default names finds nothing and reads as a clean database.

4. Host to tenant to organisation

The registration portal resolves a tenant from the request’s hostname, and the tenant carries the registration system that determines the organisation. This is the mapping that makes a public URL resolve to a set of data.

Host Tenant Registration system Organisation

hnr.myriadevents.co.za

HNR

1502

8 — Helderberg Nature Reserve

hnr-vc.myriadevents.co.za

HNR VC

1503

8 — Helderberg Nature Reserve

wcsc.myriadevents.co.za

WCSC

1504

9 — Western Cape School Cycling

registration.myriadevents.co.za

Myriad Events

1504

9 — shared with WCSC

register.tourdeworcester.co.za

Tour de Worcester

1505

10 — Myriad Events

registration-v2.myriadevents.co.za

Default V2 Tenant

1508

10 — Myriad Events

admin-portal (production and stage)

1509

10 — Myriad Events

Registration system ids differ between production and development. The same tenant carries a different id in each environment, and a development id may not exist in production at all. Always use production ids when reasoning about production. Tenant rows are operational data — only fixture-context seeds exist in the migrations, so the database is the source of truth.

5. Reading what is deployed

curl -H "X-API-KEY: $(cat ~/dev/ems/.local/secrets/admin-service-prod-admin.key)" \
     https://<host>/management/info

The production key is rejected by stage, which has its own. Both are catalogued in Credential Inventory.

6. Environment-specific behaviour

6.1. Stage cannot send mail to the outside world

Every Java service in the stage namespace points its mail host at an in-cluster GreenMail sink with authentication and TLS off. The external relay and its credentials are absent from the namespace entirely. This matters because stage’s database is refreshed from production nightly, so it holds real customer addresses — a recipient override would still leave a route out, whereas removing the relay removes the possibility.

The sink also makes delivery assertable. Query the recipients and their messages over its HTTP API on port 8080, or read mail in the browser at the stage webmail host, where any passphrase is accepted because authentication is disabled.

curl http://greenmail.event-stage.svc.cluster.local:8080/api/user
curl http://greenmail.event-stage.svc.cluster.local:8080/api/user/<email>/messages

There is no /api/mail path; it returns not-found. Development has the same pair.

6.2. Stage is reset nightly

The 04:00 SAST clone from production replaces stage’s data wholesale. Anything written to stage by hand — an API key permission, a test fixture, a corrected row — is gone the next morning unless it is written to the corresponding seed table as well. See Credential Inventory for the API key case, which is the one that recurs.

6.3. Creating tables on the production cluster

The production MySQL runs as an InnoDB cluster with Group Replication, which requires every table to have a primary key. CREATE TABLE … AS SELECT produces a table without one and is rejected. Create the table with an explicit primary key first, then populate it:

CREATE TABLE zz_working (id BIGINT NOT NULL PRIMARY KEY, ...);
INSERT INTO zz_working SELECT ... ;

mysqldump --where to a file, or a TEMPORARY table, are the other ways round it.

6.4. Running admin-service locally

The local development run uses two different sets of database credentials, which is the usual first stumble: Liquibase connects as the root user with an empty password to create the schema, while the application’s own datasource connects as the development user. Both must exist in the throwaway MySQL container. The service listens on port 12504; the registration portal uses 12505 and the admin portal 12506.