Groundfloor Docs

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=eu

Pass envVariable in the body of POST …/coderunners/{coderunner_id}/deploy. See the Coderunners API.

Read them the usual way for your language:

LanguageRead API_URL
Node.jsprocess.env.API_URL
Pythonos.environ.get("API_URL")
.NETEnvironment.GetEnvironmentVariable("API_URL")
Goos.Getenv("API_URL")
Ruststd::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.json so every gf deploy sends them. gf deploy names any variable the previous deployment had that this one doesn't send, and on a terminal gf 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 ENV lines in a Service's Dockerfile.

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_KEY or _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:

VariableValue
CONTROLPLANE_URLThe Groundfloor API's base URL
GROUNDFLOOR_WORKSPACE_IDYour workspace's ID
GROUNDFLOOR_M2M_CLIENT_ID, GROUNDFLOOR_M2M_CLIENT_SECRETThe attached client's credentials
GROUNDFLOOR_ENVIRONMENT_IDThe environment the Coderunner is bound to

Read it in your code

Exchange the client's credentials for a token, then read the secret:

secrets.js
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

SettingWhat it doesSet it from
CPU (e.g. 250m)CPU available to each instanceDeploy dialog, gf deploy --cpu, or groundfloor.json
Memory (e.g. 256Mi)Memory available to each instanceDeploy dialog, gf deploy --memory, or groundfloor.json
Minimum and maximum instancesThe range Coderunner scales within, for Functions and ServicesDeploy 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

On this page