Docs / Core Concepts / Environment Variables

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

  1. Navigate to your app (or stack for shared variables).
  2. Open the Environment tab.
  3. Click Add Variable.
  4. Enter the variable name and value.
  5. Click Save.
Environment tab showing a form to add a new variable with Name and Value fields, plus a list of existing variables with masked values and edit/delete buttons
Adding environment variables from the dashboard
Note
Changes to environment variables require a redeployment to take effect. sh0 will prompt you to redeploy after saving changes, or you can redeploy manually when ready.

From the API

Terminal
# 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.

Algorithm

AES-256-GCM

Key Derivation

HKDF from master key

Nonce

Unique per value (96-bit)

Library

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.

Back Up Your Encryption Key
If you lose the encryption key (stored in the sh0 data directory), you will not be able to decrypt your environment variables. Make sure your backup strategy includes the entire sh0 data directory, not just the database file.

Bulk Import (.env File)

If you have an existing .env file, you can import all variables at once:

  1. Go to the Environment tab of your app or stack.
  2. Click Import .env.
  3. Either paste the contents of your .env file or upload the file directly.
  4. Review the parsed variables -- sh0 will show you what will be added, updated, or skipped.
  5. Click Import to apply.
Import .env dialog showing a text area with pasted env file contents on the left, and a parsed preview on the right showing 6 variables to be added and 2 to be updated, with checkboxes to include/exclude each
Importing variables from a .env file

The import parser supports standard .env syntax including:

Supported .env Syntax
# 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:

Diagram showing variable scoping: Stack level (DATABASE_URL, REDIS_URL) flows down to App A and App B, while App A has its own PORT=3000 and App B has PORT=8000, overriding any stack-level PORT value
Stack variables are inherited by all apps, with app-level overrides

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
Override Example
If a stack defines 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:

Variable References
# 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:

Common Pattern
# 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:6379
Note
The hostname is the app's name within the stack. If your app is named api, 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:

VariableExampleDescription
SH0_APP_NAMEapiThe app's name within the stack
SH0_STACK_NAMEmy-saasThe parent stack's name
SH0_DEPLOY_IDd-abc123Unique identifier for this deployment
SH0_DEPLOY_SHAa1b2c3dGit commit SHA of the deployed code
SH0_DOMAINapi.example.comThe primary domain assigned to this app
Tip
System variables are useful for logging, error tracking, and conditional logic. For example, you can include 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.

A .env.example file in a code editor showing variable names with placeholder descriptions: DATABASE_URL='postgres://...', REDIS_URL='redis://...', SECRET_KEY='generate-a-random-key'
Document required variables with a .env.example file