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:
- Regular Variables: Standard configuration values
- Secrets: Encrypted sensitive data (passwords, API keys)
- 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
- Never commit secrets to version control
- Always mark sensitive values with
secret: true - Leave
valueempty 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:
- Platform-injected variables (
STRONGLY_*,PORT) always win. - Manifest values override values entered in the deploy form. To let users set a variable at deploy time, leave its
valueempty in the manifest. - 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
| Variable | Description | Example |
|---|---|---|
STRONGLY_URL | Relative proxy path for the app (no domain) | /api/proxy/app-xyz123 |
STRONGLY_HOST | Platform host URL (no trailing slash) | https://app.strongly.ai |
STRONGLY_APP_ID | Unique Kubernetes-friendly app identifier | app-xyz123 |
STRONGLY_SERVICES | JSON with all connected services | See STRONGLY_SERVICES |
PORT | The port your app must listen on | 8080 |
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:
| Header | Description |
|---|---|
X-Strongly-User-Token | Signed token identifying the signed-in user. Absent for anonymous visitors to a public app. |
X-Forwarded-Prefix | The 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 appadmin: User with admin-level access to the appuser: User with standard access to the appviewer: 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 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
- Navigate to Apps and click Deploy App
- Add environment variables as key/value pairs in the deploy form, including values for your required secrets
- Deploy the application
After Deployment
- Navigate to the app details page
- Click the Settings tab to see the app's environment variables
- Stop the app to make the values editable, then update them
- Start the app - changes take effect when the app is started
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: truein 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: trueis set in manifest - Add
ARGdirective in Dockerfile - Check build logs for variable injection