Skip to main content

Environment Variables & Configuration

Learn how to configure your applications using environment variables, secrets, and runtime configuration.

Environment Variable Types​

The Strongly platform supports three types of environment variables:

  1. Regular Variables: Standard configuration values
  2. Secrets: Encrypted sensitive data (passwords, API keys)
  3. Build-time Variables: Values injected during Docker build

Defining Variables in Manifest​

Regular Environment Variables​

Standard configuration values accessible at runtime:

env:
- name: API_URL
value: "https://api.example.com"
required: false
description: Backend API endpoint

- name: LOG_LEVEL
value: "info"
required: false
description: Application log level (debug, info, warn, error)

Secrets​

Sensitive values marked with secret: true. Secret values are never baked into the built image and are excluded from client-side runtime config injection:

env:
- name: DATABASE_PASSWORD
value: "" # Provided by user during deployment
required: true
secret: true
description: PostgreSQL database password

- name: JWT_SECRET
value: ""
required: true
secret: true
description: Secret for JWT token signing

- name: API_KEY
value: ""
required: true
secret: true
description: Third-party API key
Security Best Practices
  • Never commit secrets to version control
  • Always mark sensitive values with secret: true
  • Leave value empty for secrets - users provide them during deployment
  • Rotate secrets regularly

Build-time Variables​

Values passed to Docker build as --build-arg:

env:
- name: NODE_ENV
value: "production"
buildtime: true
description: Node.js environment

- name: REACT_APP_VERSION
value: "1.0.0"
buildtime: true
description: Application version displayed in UI

- name: REACT_APP_API_URL
value: "https://api.acme.com"
buildtime: true
description: Baked into React build

Using in Dockerfile:

ARG NODE_ENV
ARG REACT_APP_VERSION
ARG REACT_APP_API_URL

ENV NODE_ENV=${NODE_ENV}
ENV REACT_APP_VERSION=${REACT_APP_VERSION}
ENV REACT_APP_API_URL=${REACT_APP_API_URL}

RUN npm run build

Variable Precedence​

Environment variables can come from multiple sources. When the same name is set in more than one place:

  1. Platform-injected variables (STRONGLY_*, PORT) always win.
  2. Manifest values override values entered in the deploy form. To let users set a variable at deploy time, leave its value empty in the manifest.
  3. Deploy-form values apply when the manifest does not pin a value.

Platform-Injected Variables​

The platform sets these variables in every app:

Core Platform Variables​

VariableDescriptionExample
STRONGLY_URLRelative proxy path for the app (no domain)/api/proxy/app-xyz123
STRONGLY_HOSTPlatform host URL (no trailing slash)https://app.strongly.ai
STRONGLY_BASE_URLThe platform's public URL (no trailing slash), for links shown to a person; the same in every app, workspace and jobhttps://app.strongly.ai
STRONGLY_API_URLThe platform API's address from inside the app (append /api/v1). Calls to it are made as the app's owner, with no API keyhttp://dashboard.strongly.svc.cluster.local
STRONGLY_PLATFORM_AUTHauth-proxy: the app sends no key of its own to STRONGLY_API_URL; the Strongly SDK reads thisauth-proxy
STRONGLY_APP_IDThe app's unique IDapp-xyz123
STRONGLY_SERVICESJSON with all connected servicesSee STRONGLY_SERVICES
PORTThe port your app listens on: the one your manifest declares3000

These are set last: an environment variable or manifest entry with the same name does not replace STRONGLY_API_URL, STRONGLY_PLATFORM_AUTH or STRONGLY_BASE_URL.

During image builds, the platform also passes the size you typed as STRONGLY_CPU, STRONGLY_MEMORY, and STRONGLY_DISK Docker build arguments (each only when set, for example 0.5 / 1GB / 10GB), along with the STRONGLY_* identity variables above.

STRONGLY_URL (Critical for Frontend Apps)​

STRONGLY_URL is a relative path (e.g., /api/proxy/app-xyz123), not a full URL. It provides the base proxy path through which requests reach your app.

When your app is displayed in the platform's iframe, frontend code needs to make API calls through the proxy. STRONGLY_URL provides the correct base path.

Why this matters: Apps are accessed via /apps/app-xyz/view. Without STRONGLY_URL:

  • API calls go to /api/... (wrong - hits the platform, not your app)
  • With STRONGLY_URL, API calls go to /api/proxy/app-xyz/api/... (correct - proxied to your app)

Server-side: Inject runtime config into HTML

// In your Express server's static file handler
app.get('*', (req, res) => {
const indexPath = path.join(__dirname, '../client/dist/index.html');
fs.readFile(indexPath, 'utf8', (err, data) => {
if (err) return res.status(500).send('Server error');

// Build runtime config from platform environment variables
const runtimeConfig = {
STRONGLY_URL: process.env.STRONGLY_URL || '',
STRONGLY_HOST: process.env.STRONGLY_HOST || '',
STRONGLY_APP_ID: process.env.STRONGLY_APP_ID || '',
API_URL: '/api'
};

// Inject into HTML
const modifiedHtml = data.replace(
'</head>',
`<script>window.__RUNTIME_CONFIG__ = ${JSON.stringify(runtimeConfig)};</script></head>`
);

res.send(modifiedHtml);
});
});

Client-side: Use runtime config for API calls

// In your API service (e.g., api.ts)
function getApiBaseUrl() {
const config = window.__RUNTIME_CONFIG__ || {};
if (config.STRONGLY_URL) {
// STRONGLY_URL = /api/proxy/app-xyz (relative path)
// Append /api for your backend API routes
return `${config.STRONGLY_URL.replace(/\/$/, '')}/api`;
}
// Fallback for local development
return '/api';
}

const api = axios.create({
baseURL: getApiBaseUrl(),
timeout: 30000,
});

STRONGLY_SERVICES​

JSON object containing all connected service credentials. See STRONGLY_SERVICES Integration for the complete structure and usage examples.

const services = JSON.parse(process.env.STRONGLY_SERVICES || '{}');

// Access addons (grouped by type)
const mongoAddons = services.services?.addons?.mongodb || [];
const firstMongo = mongoAddons[0];

// Access AI models
const aiGateway = services.services?.aigateway;
const models = aiGateway?.available_models || [];

User Identity Headers​

The platform proxy adds identity headers to requests reaching your app, so your app never needs its own login page, user table, or session store:

HeaderDescription
X-Strongly-User-TokenSigned token identifying the signed-in user. Absent for anonymous visitors to a public app.
X-Forwarded-PrefixThe proxy base path for your app (/api/proxy/<app-id>)

The user token is a standard three-segment token whose payload segment is base64url-encoded JSON containing a user object with id, email, name, and role. On an app that charges for access, a covered user's user object also carries plan (id, kind of individual or team, and status), so the app can gate features by plan.

Role values (in the token's user.role claim):

  • owner: The user who created/owns the app
  • admin: User with admin-level access to the app
  • user: User with standard access to the app
  • viewer: Authenticated user of an app that allows all users

Reading the user in Express:

// Decode the payload of the proxy-injected user token
function decodeUserToken(token) {
const parts = token.split('.');
if (parts.length !== 3) {
throw new Error('X-Strongly-User-Token is not in the expected format');
}
return JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
}

app.use((req, res, next) => {
const token = req.headers['x-strongly-user-token'];
if (!token) {
// Anonymous access to a public app - a legitimate state, not an error
req.user = null;
return next();
}
try {
req.user = decodeUserToken(token).user; // { id, email, name, role }
} catch (err) {
return res.status(400).json({ error: 'Malformed user token' });
}
next();
});

// Use in routes
app.get('/api/profile', (req, res) => {
if (!req.user) {
return res.status(401).json({ error: 'Not authenticated' });
}
res.json({ user: req.user });
});

// Check for owner/admin access
app.delete('/api/settings', (req, res) => {
if (!req.user || !['owner', 'admin'].includes(req.user.role)) {
return res.status(403).json({ error: 'Admin access required' });
}
// ... handle admin action
});

Signing Out​

A link to _strongly/sign-out under the app's own address (<X-Forwarded-Prefix>/_strongly/sign-out) ends the user's platform session and shows them the app's sign-in page. Give it target="_top" so it replaces the whole page, not only the frame the app runs in:

<a href="_strongly/sign-out" target="_top">Sign out</a>

See Branded Sign-in & Sign-up for the script form and the app's own sign-out page.

Accessing Variables in Code​

Node.js​

// Access individual variables
const nodeEnv = process.env.NODE_ENV;
const apiKey = process.env.API_KEY;

// Parse STRONGLY_SERVICES
const services = JSON.parse(process.env.STRONGLY_SERVICES || '{}');

// Access a MongoDB addon
const mongoAddons = services.services?.addons?.mongodb || [];
if (mongoAddons.length > 0) {
const mongo = mongoAddons[0];
const connectionString = mongo.connection?.connection_string;
}

Python​

import os
import json

# Access individual variables
flask_env = os.environ.get('FLASK_ENV', 'development')
api_key = os.environ.get('API_KEY')

# Parse STRONGLY_SERVICES
services = json.loads(os.environ.get('STRONGLY_SERVICES', '{}'))

# Access a MongoDB addon
mongo_addons = services.get('services', {}).get('addons', {}).get('mongodb', [])
if mongo_addons:
mongo = mongo_addons[0]
connection_string = mongo.get('connection', {}).get('connection_string')

React (Build-time)​

Variables prefixed with REACT_APP_ are embedded at build time:

// Access in React components
const apiUrl = process.env.REACT_APP_API_URL;
const version = process.env.REACT_APP_VERSION;

console.log('API URL:', apiUrl);
React Environment Variables

React environment variables must be prefixed with REACT_APP_ and marked as buildtime: true in the manifest.

Runtime Configuration Injection​

For React apps (type: react with a static.config_file configured), the platform injects runtime configuration without rebuilding:

Manifest Configuration​

type: react
static:
root: /app/build
config_file: /app/build/config.js
config_placeholder: __APP_CONFIG__

Template File​

Create public/config.js with placeholder:

window.__APP_CONFIG__ = __APP_CONFIG__;

HTML Reference​

Include in public/index.html:

<!DOCTYPE html>
<html>
<head>
<script src="%PUBLIC_URL%/config.js"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>

Access in React​

const config = window.__APP_CONFIG__;

function App() {
const apiUrl = config.REACT_APP_API_URL;

return <div>API: {apiUrl}</div>;
}

At container start, the platform writes the actual values into the config file and replaces __APP_CONFIG__ in index.html:

window.__APP_CONFIG__ = {
"REACT_APP_API_URL": "https://api.acme.com"
};

Only runtime variables are injected: build-time variables (buildtime: true) and secrets (secret: true) are excluded from the runtime config.

Updating Variables​

During Deployment​

  1. Navigate to Apps and click Deploy App
  2. Add environment variables as key/value pairs in the deploy form, including values for your required secrets
  3. Deploy the application

After Deployment​

  1. Navigate to the app details page
  2. Click the Settings tab to see the app's environment variables
  3. Stop the app to make the values editable, then update them
  4. Start the app - changes take effect when the app is started
Restart Required

Environment variables can only be edited while the app is stopped, and changes take effect on the next start.

Validation​

How the platform handles your manifest's environment variables:

  • Required variables without a value are flagged during deployment
  • Secret variables are never baked into the built image or exposed in client-side runtime config
  • Build-time variables are passed to the Docker build as build arguments

Best Practices​

Naming Conventions​

env:
# Good: Uppercase with underscores
- name: DATABASE_URL
- name: JWT_SECRET
- name: MAX_CONNECTIONS

# Bad: Lowercase or mixed case
- name: databaseUrl # Avoid
- name: jwtSecret # Avoid

Secret Management​

env:
# Store as secret
- name: DB_PASSWORD
secret: true
required: true

# Store as regular variable
- name: DB_HOST
value: "postgres.example.com"
secret: false

Documentation​

env:
- name: CACHE_TTL
value: "300"
description: Cache time-to-live in seconds

- name: MAX_RETRIES
value: "3"
description: Maximum number of retry attempts for failed requests

Troubleshooting​

Variable Not Found​

Problem: Application can't find environment variable

Solutions:

  • Check variable is defined in manifest
  • Verify variable name matches exactly (case-sensitive)
  • Ensure app was redeployed after adding variable
  • Check the app's Runtime Logs for startup errors

Secret Value Empty​

Problem: A secret variable is empty at runtime

Solutions:

  • Verify the secret value was provided during deployment
  • Check the variable is marked with secret: true in the manifest
  • Restart the application if the value was added or changed later

Build-time Variable Not Available​

Problem: Variable not available during Docker build

Solutions:

  • Ensure buildtime: true is set in manifest
  • Add ARG directive in Dockerfile
  • Check build logs for variable injection

Next Steps​