Configuring Development and Production Environments
The prototype framework has two settings that control how it behaves in development versus production. This guide explains what those settings do, how the framework loads its configuration, and what you need to change when moving from a local setup to a deployed application.
The two key settings
global.debugMode controls how errors are reported. When set to true, the API returns detailed error messages including a full stack trace, which the frontend displays when you click on a red error notification. When set to false, the error message reads "An error occurred (debug information in server log files)" and the stack trace stays in the server logs. The default value is true.
global.productionEnv controls which management operations the framework allows. When set to true, reinstalling the database is permanently blocked. If the application starts and the database does not exist or is out of date, the framework throws an exception and will not start. This prevents accidental data loss in a deployed environment. The default value is false.
global.serviceKey opens those same management operations to a machine — a deployment pipeline, for instance — while they stay closed for everyone else. The section "The service key" below describes how it works. The default value is null, which keeps production mode as strict as it has always been.
How the framework loads configuration
The framework loads configuration in the following order. Each step can overwrite values from the previous step.
src/Ampersand/Misc/defaultSettings.yaml— the framework's built-in defaultsgenerics/settings.json— settings generated by the Ampersand compiler from your ADL modelconfig/project.yaml— your project-specific settings- Environment variables — loaded last, always take priority
This means environment variables always win. You do not need to change a config file to switch between environments; setting the right environment variables is enough.
Environment variables
The following environment variables map directly to configuration settings:
| Environment variable | Setting key | Type |
|---|---|---|
AMPERSAND_DEBUG_MODE | global.debugMode | boolean |
AMPERSAND_PRODUCTION_MODE | global.productionEnv | boolean |
AMPERSAND_SERVICE_KEY | global.serviceKey | string |
AMPERSAND_SERVER_URL | global.serverURL | string |
AMPERSAND_DATA_DIR | global.dataPath | string |
AMPERSAND_DBHOST | mysql.dbHost | string |
AMPERSAND_DBNAME | mysql.dbName | string |
AMPERSAND_DBUSER | mysql.dbUser | string |
AMPERSAND_DBPASS | mysql.dbPass | string |
AMPERSAND_SESSION_COOKIE_SECURE | session.cookieSecure | string: auto, true or false |
Boolean values are parsed with PHP's FILTER_VALIDATE_BOOLEAN. The strings "true", "1", "yes", and "on" all result in true; "false", "0", "no", and "off" result in false.
Settings overview
The table below lists the settings relevant to environment configuration, with their default values and recommended values for development and production.
| Setting | Default | Development | Production |
|---|---|---|---|
global.debugMode | true | true | false |
global.productionEnv | false | false | true |
global.serviceKey | null | null | null, or a secret |
global.serverURL | http://localhost | local address | public domain |
global.scriptTimeout | 30 (seconds) | 0 (no limit) | 30 or higher |
mysql.dbHost | localhost | container name | production host |
mysql.dbPass | ampersand | dev password | strong password |
session.cookieSecure | auto | auto | true |
frontend.privacyStatementUrl | null | null | URL of the privacy statement |
session.cookieSecure sets the Secure flag of the session cookie, which keeps the browser from sending it over plain HTTP. With auto the flag follows the request: set over HTTPS, also behind a reverse proxy that sends X-Forwarded-Proto: https. Production uses true, so that the flag does not depend on the network in front of the application. frontend.privacyStatementUrl puts a link to the privacy statement of the deploying organisation in the footer; Cookies and browser storage lists what that statement should mention.
Setting variables in Docker Compose
The most straightforward way to switch between development and production is to set environment variables in your compose.yaml or docker-compose.yml:
services:
prototype:
environment:
- AMPERSAND_DEBUG_MODE=true
- AMPERSAND_PRODUCTION_MODE=false
- AMPERSAND_SERVER_URL=http://localhost
For production, change to:
services:
prototype:
environment:
- AMPERSAND_DEBUG_MODE=false
- AMPERSAND_PRODUCTION_MODE=true
- AMPERSAND_SERVER_URL=https://your-domain.example.com
- AMPERSAND_SESSION_COOKIE_SECURE=true
Keep database passwords out of version control. Pass them via a .env file that is listed in .gitignore:
- AMPERSAND_DBPASS=${MYSQL_AMPERSAND_PASSWORD}
Setting values in project.yaml
You can set values in config/project.yaml for settings that are the same across all deployments of the same project. The file is loaded after the framework defaults and the compiler-generated settings, but before environment variables. A typical use is to raise the script timeout for projects that import large data files:
settings:
global.scriptTimeout: 0
Environment variables always override values in this file, so you can use project.yaml for project-wide defaults and environment variables for per-deployment overrides.
Role-based access control
Three RBAC settings control who can reach administrative functions. All three default to null.
rbac.adminRoles names the roles that may use the Installer, the ExecEngine runner, the rule checker, and the population exporter. When set to null, every user has admin access. When set to an empty list [], nobody has access. When set to a list of role names, only sessions that have one of those roles active can reach those functions.
rbac.importerRoles names the roles that may upload population files (Excel, JSON). The same logic applies: null means unrestricted, [] means nobody, a list restricts to named roles.
rbac.accessibleInterfacesIfcId is optional. It names an interface whose expression determines which interfaces are accessible in the current session. If left null, the framework falls back to querying pf_ifcRoles[PF_Interface*Role] for every active role.
These settings belong in config/project.yaml:
settings:
rbac.adminRoles: [Administrator]
rbac.importerRoles: [Administrator, Importer]
The combined effect of productionEnv and role settings
Production mode and role settings interact in two ways.
The Installer menu item is only shown when both conditions hold: the application is not in production mode, and the user has an active admin role. Even if only one condition fails, the item disappears from the menu. The same applies to the population exporter.
At the API level, all installer and exporter endpoints call preventProductionMode() before checking roles. A request to these endpoints in production mode is blocked regardless of the caller's role, unless it carries the service key described in the next section. The population importer does not call preventProductionMode(), so it remains available in production — gated only by rbac.importerRoles.
| Function | Blocked by productionEnv | Gated by role |
|---|---|---|
| Reinstall database | yes | no role check |
| Export all population | yes | rbac.adminRoles |
| Export selection | yes | rbac.adminRoles |
| Reports and metamodel | yes | rbac.adminRoles |
| Test login | yes | no role check |
| Import population file | no | rbac.importerRoles |
| Run ExecEngine | no | rbac.adminRoles |
| Check rules | no | rbac.adminRoles |
Every "yes" in the middle column turns into "no" for a request that carries the service key. The role check in the right-hand column stays: a machine that reaches an endpoint with the key still needs an active role when rbac.adminRoles names one.
The service key
A deployed application in production mode closes its administrative endpoints to everyone. A deployment pipeline needs exactly those endpoints: it exports the population and runs the installer to migrate the data when the model changes. The service key resolves that conflict. It lets a machine through the production-mode gate while the application stays closed for people.
Set the key with the environment variable AMPERSAND_SERVICE_KEY, next to AMPERSAND_PRODUCTION_MODE:
services:
prototype:
environment:
- AMPERSAND_PRODUCTION_MODE=true
- AMPERSAND_SERVICE_KEY=${AMPERSAND_SERVICE_KEY}
Keep the value out of version control, the same way you keep the database password out of it: put it in a .env file that .gitignore lists, or in the secret store of your deployment platform. Generate a value that nobody has to remember, for example with openssl rand -hex 32.
A request presents the key in the X-Ampersand-Service-Key header:
curl -H "X-Ampersand-Service-Key: $AMPERSAND_SERVICE_KEY" \
"https://your-domain.example.com/api/v1/admin/exporter/export/all"
The header keeps the key out of the places a URL is written down: the access log of the web server, the log records of the framework itself, the browser history, and the referrer of a next request. A key in a query parameter would end up in all of them.
Three properties are worth knowing before you rely on this.
The mechanism fails closed. Without a configured key — the default — production mode refuses every request to these endpoints, exactly as it did before the key existed. A key that is empty or consists of whitespace only counts as no key at all, and a request that presents such a value is refused as well.
A refused request always gets the same answer, whatever the reason. The caller cannot tell a wrong key from an application that has no key configured.
The key never appears in a log line, an error message or a response. The framework masks it where it masks the database password, and the refusal message names no value.
What the key does not do is replace a role. It lifts the production-mode gate and nothing else. When rbac.adminRoles names a role, the export endpoints still require a session with that role active. Grant the machine that role, or leave rbac.adminRoles at null, and treat the key as the credential it is: one key per deployment, rotated by changing the environment variable and restarting the container.
Warning: starting production mode with an empty database
When global.productionEnv is true and the database does not yet exist, the framework will not create it. It throws an exception on startup. You must install the database first — using the Installer in the frontend — while running in non-production mode, then switch to production mode.