Configuration and secrets
Give your Coderunner settings, read secrets safely at runtime, and set CPU, memory and scaling.
A deployment carries three kinds of configuration: environment variables for ordinary settings, secrets read at runtime, and the resources your code runs with.
Environment variables
Use environment variables for settings that aren't secret: URLs, feature switches, region names. Set them when you deploy:
Add them in the deploy dialog, as JSON:
{ "API_URL": "https://api.example.com", "REPORT_REGION": "eu" }gf deploy -e API_URL=https://api.example.com -e REPORT_REGION=euPass envVariable in the body of POST …/coderunners/{coderunner_id}/deploy. See the Coderunners API.
Read them the usual way for your language:
| Language | Read API_URL |
|---|---|
| Node.js | process.env.API_URL |
| Python | os.environ.get("API_URL") |
| .NET | Environment.GetEnvironmentVariable("API_URL") |
| Go | os.Getenv("API_URL") |
| Rust | std::env::var("API_URL") |
Deploy them every time
Groundfloor doesn't keep the values of your variables between deploys, only their names. Each deploy runs with exactly the variables you send with it:
- With the CLI, keep them in
groundfloor.jsonso everygf deploysends them.gf deploynames any variable the previous deployment had that this one doesn't send, and on a terminalgf deploy -c <coderunner>asks before deploying without it. See Confirmations. - In the Client Portal, enter them in the deploy dialog each time.
- Deploys started automatically — a push to a connected Git repository, or promotion to another environment — send no variables. Settings your code always needs belong in the code or the image: a config file in your ZIP, or
ENVlines in a Service'sDockerfile.
Names that are never passed
To stop credentials leaking through deploy settings, Groundfloor rejects a deploy that sends variables whose names look like secrets. The API answers 422 and lists the offending names, and the gf CLI refuses before it sends anything. The variables are not silently dropped, so remove them from your deploy and the deploy goes through. These names are rejected:
- names ending in
_PASSWORD,_SECRET,_SECRET_KEY,_PRIVATE_KEY,_API_KEYor_ACCESS_KEY - names starting with
MANAGED_DB_ DATABASE_URL,DATABASE_URI,DATABASE_USER,DATABASE_USERNAME,DATABASE_PASSWORD,DB_URL,DB_URI,POSTGRES_URL,POSTGRES_URI,POSTGRES_USER,POSTGRES_PASSWORD,MYSQL_URL,MYSQL_PASSWORD,MONGO_URI,MONGODB_URI,MONGODB_URL
Read those from Secrets at runtime instead, as below.
The error suggests gf secrets set KEY with gf deploy --secret KEY, which would copy a secret into the deployment as a variable. That option is switched off across the platform (it is a platform-wide setting, currently off), so don't rely on it: a deploy that asks for it fails with 422. Reading secrets at runtime, as below, always works.
Secrets
Don't pass secrets at deploy time. Store API keys, passwords and tokens in Secrets, and have your code read them when it runs. They never appear in the deployment's configuration, and you can rotate a secret in one place.
Never put a secret in your code, your ZIP, a .env file, your logs or a response.
Set it up
Store the secret
In the Client Portal, go to Data → Secrets and add it, or run gf secrets set STRIPE_API_KEY - and paste the value, then press Ctrl+D.
Give the Coderunner an M2M client that can read secrets
Go to Administer → M2M tokens, create a client with Read secrets turned on, and attach it to the Coderunner. See M2M tokens.
Deploy again
Your code now has these environment variables:
| Variable | Value |
|---|---|
CONTROLPLANE_URL | The Groundfloor API's base URL |
GROUNDFLOOR_WORKSPACE_ID | Your workspace's ID |
GROUNDFLOOR_M2M_CLIENT_ID, GROUNDFLOOR_M2M_CLIENT_SECRET | The attached client's credentials |
GROUNDFLOOR_ENVIRONMENT_ID | The environment the Coderunner is bound to |
Read it in your code
Exchange the client's credentials for a token, then read the secret:
const base = `${process.env.CONTROLPLANE_URL}/v1/workspaces/${process.env.GROUNDFLOOR_WORKSPACE_ID}`;
let cached; // { token, expiresAt }
async function token() {
if (cached && Date.now() < cached.expiresAt) return cached.token;
const res = await fetch(`${base}/m2m/token`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
client_id: process.env.GROUNDFLOOR_M2M_CLIENT_ID,
client_secret: process.env.GROUNDFLOOR_M2M_CLIENT_SECRET,
}),
});
const { access_token, expires_in } = await res.json();
cached = { token: access_token, expiresAt: Date.now() + expires_in * 800 }; // refresh at 80% of its life
return access_token;
}
async function getSecret(key) {
const env = process.env.GROUNDFLOOR_ENVIRONMENT_ID ?? "";
const res = await fetch(`${base}/secrets/${key}?environment_id=${encodeURIComponent(env)}`, {
headers: { authorization: `Bearer ${await token()}` },
});
return (await res.json()).value;
}
module.exports = { getSecret };Pass environment_id to choose which environment's secrets to read: GROUNDFLOOR_ENVIRONMENT_ID is the Coderunner's own. Without it, Groundfloor reads the workspace's dev environment (or its only environment), so a production Service would get your development keys. environment_id doesn't limit what the code can read: an M2M client with Read secrets can read the secrets of every environment in the workspace.
Reuse the token until shortly before it expires. Minting a new one on every request runs into the token endpoint's rate limit (20 a minute by default) and starts failing with 429.
In a container you didn't write
A Service running someone else's image can't call the Secrets API itself. Read the secret in a small entrypoint script, then start the application. See Example: run WordPress.
Resources and scaling
| Setting | What it does | Set it from |
|---|---|---|
CPU (e.g. 250m) | CPU available to each instance | Deploy dialog, gf deploy --cpu, or groundfloor.json |
Memory (e.g. 256Mi) | Memory available to each instance | Deploy dialog, gf deploy --memory, or groundfloor.json |
| Minimum and maximum instances | The range Coderunner scales within, for Functions and Services | Deploy dialog or the API (minReplicas, maxReplicas). The CLI has no flag for this yet |
If you don't set CPU and memory, a deploy uses 250m and 256Mi. Minimum and maximum instances can each be from 0 to 100.
A minimum of 0 lets an idle Function scale to zero; a minimum of 1 or more keeps it warm. A Service that should always answer quickly needs a minimum of at least 1.
Next steps
- Deploying and versions — redeploy to apply changes.
- Secrets — manage secrets and environments.
- M2M tokens — scopes, rotation and rate limits.