Environment Variables
Environment variables are the primary way to configure your applications in sh0. All values are encrypted at rest using AES-256-GCM, and you can scope them to individual apps or share them across an entire stack.
Adding Environment Variables
You can add environment variables through the dashboard or the API. Variables are injected into your containers at startup and are available to your application as standard environment variables.
From the Dashboard
- Navigate to your app (or stack for shared variables).
- Open the Environment tab.
- Click Add Variable.
- Enter the variable name and value.
- Click Save.
From the API
# Add a single variable
curl -X POST http://localhost:9000/api/apps/APP_ID/env \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"key": "DATABASE_URL", "value": "postgres://user:pass@db:5432/myapp"}'
# Add multiple variables at once
curl -X POST http://localhost:9000/api/apps/APP_ID/env/bulk \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"variables": [
{"key": "NODE_ENV", "value": "production"},\n {"key": "PORT", "value": "3000"},\n {"key": "SECRET_KEY", "value": "your-secret-key"}
]}'Encryption at Rest
All environment variable values are encrypted before being stored in the sh0 database. sh0 uses AES-256-GCM (Galois/Counter Mode) encryption with a unique nonce per value.
AES-256-GCM
HKDF from master key
Unique per value (96-bit)
ring (Rust)
This means that even if someone gains access to the sh0 database file, they cannot read your secrets without the encryption key. The master key is generated on first startup and stored separately from the database.
Bulk Import (.env File)
If you have an existing .env file, you can import all variables at once:
- Go to the Environment tab of your app or stack.
- Click Import .env.
- Either paste the contents of your .env file or upload the file directly.
- Review the parsed variables -- sh0 will show you what will be added, updated, or skipped.
- Click Import to apply.
The import parser supports standard .env syntax including:
# Comments are ignored
DATABASE_URL=postgres://user:pass@db:5432/myapp
SECRET_KEY="value with spaces"
MULTILINE="line1\nline2"
# Empty values are supported
EMPTY_VAR=
# Quotes are stripped automatically
QUOTED='single quotes work too'Variable Scoping
Environment variables can be defined at two levels, and the more specific scope always wins:
Stack-Level Variables
Stack-level variables are inherited by every app in the stack. Use them for shared configuration:
- Database connection strings shared by multiple apps
- API keys used across the project
- Environment identifiers (
APP_ENV=production) - Feature flags that apply project-wide
App-Level Variables
App-level variables are specific to a single app and override any stack-level variable with the same name:
- Port numbers (
PORT=3000) - App-specific API keys
- Configuration that differs between the frontend and backend
LOG_LEVEL=info and one app defines LOG_LEVEL=debug, that specific app runs with debug logging while all other apps in the stack use info.Referencing Between Apps
Apps within the same stack can communicate using their app name as a hostname on the internal Docker network. sh0 also provides a way to reference one app's variables from another using template syntax:
# In the 'frontend' app, reference the 'api' app's port:
API_URL=http://api:${api.PORT}
# Reference the database connection from the 'db' service:
DATABASE_HOST=${db.HOSTNAME}The most common pattern is using the internal hostname directly, since all apps in a stack share a Docker network:
# In your frontend app's env vars:
API_URL=http://api:3000
# In your API app's env vars:
DATABASE_URL=postgres://user:pass@db:5432/myapp
REDIS_URL=redis://cache:6379api, other apps in the same stack can reach it at http://api:PORT.System Variables
sh0 automatically injects a set of read-only system variables into every container:
| Variable | Example | Description |
|---|---|---|
| SH0_APP_NAME | api | The app's name within the stack |
| SH0_STACK_NAME | my-saas | The parent stack's name |
| SH0_DEPLOY_ID | d-abc123 | Unique identifier for this deployment |
| SH0_DEPLOY_SHA | a1b2c3d | Git commit SHA of the deployed code |
| SH0_DOMAIN | api.example.com | The primary domain assigned to this app |
SH0_DEPLOY_SHA in your error reports to correlate issues with specific deployments.Best Practices
Use stack-level variables for shared config
If multiple apps need the same database URL or API key, define it at the stack level instead of duplicating it in each app. This keeps your configuration DRY and easier to update.
Never commit secrets to Git
Use sh0 environment variables for all secrets (API keys, database passwords, tokens). Add .env to your .gitignore and import variables through the dashboard instead.
Use descriptive variable names
Prefer STRIPE_SECRET_KEY over KEY or SK. Clear names make it easier to manage variables as your project grows and new team members join.
Document required variables
Include a .env.example file in your repository with all required variable names (without actual values). This serves as documentation for anyone deploying the project.