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 automatically injects these variables into all app pods:

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_APP_IDUnique Kubernetes-friendly app identifierapp-xyz123
STRONGLY_SERVICESJSON with all connected servicesSee STRONGLY_SERVICES
PORTThe port your app must listen on8080

During image builds, the platform also passes STRONGLY_CPU, STRONGLY_MEMORY, and STRONGLY_DISK (the app's resource allocation, e.g. 0.5 / 1GB / 5GB) as Docker build arguments, 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.

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 accessing a public app

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
});

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 pod 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