Skip to main content

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.

  1. src/Ampersand/Misc/defaultSettings.yaml — the framework's built-in defaults
  2. generics/settings.json — settings generated by the Ampersand compiler from your ADL model
  3. config/project.yaml — your project-specific settings
  4. 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 variableSetting keyType
AMPERSAND_DEBUG_MODEglobal.debugModeboolean
AMPERSAND_PRODUCTION_MODEglobal.productionEnvboolean
AMPERSAND_SERVICE_KEYglobal.serviceKeystring
AMPERSAND_SERVER_URLglobal.serverURLstring
AMPERSAND_DATA_DIRglobal.dataPathstring
AMPERSAND_DBHOSTmysql.dbHoststring
AMPERSAND_DBNAMEmysql.dbNamestring
AMPERSAND_DBUSERmysql.dbUserstring
AMPERSAND_DBPASSmysql.dbPassstring
AMPERSAND_SESSION_COOKIE_SECUREsession.cookieSecurestring: 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.

SettingDefaultDevelopmentProduction
global.debugModetruetruefalse
global.productionEnvfalsefalsetrue
global.serviceKeynullnullnull, or a secret
global.serverURLhttp://localhostlocal addresspublic domain
global.scriptTimeout30 (seconds)0 (no limit)30 or higher
mysql.dbHostlocalhostcontainer nameproduction host
mysql.dbPassampersanddev passwordstrong password
session.cookieSecureautoautotrue
frontend.privacyStatementUrlnullnullURL 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.

FunctionBlocked by productionEnvGated by role
Reinstall databaseyesno role check
Export all populationyesrbac.adminRoles
Export selectionyesrbac.adminRoles
Reports and metamodelyesrbac.adminRoles
Test loginyesno role check
Import population filenorbac.importerRoles
Run ExecEnginenorbac.adminRoles
Check rulesnorbac.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.