Environment Variables
Configure environment variables for your deployed applications using multiple methods.
Plain Text Values
The simplest way to set environment variables:
name: "my-app"
env:
- name: "DATABASE_URL"
value: "postgres://localhost:5432/myapp"
- name: "DEBUG"
value: "true"
- name: "MAX_CONNECTIONS"
value: "100"
name: "my-app"
env:
- name: "DATABASE_URL"
value: "postgres://localhost:5432/myapp"
- name: "DEBUG"
value: "true"
- name: "MAX_CONNECTIONS"
value: "100"
From Environment Variables
Reference environment variables from your local machine:
name: "my-app"
env:
- name: "DATABASE_URL"
from:
env: "PRODUCTION_DATABASE_URL"
- name: "API_KEY"
from:
env: "MY_API_KEY"
- name: "SECRET_KEY"
from:
env: "APP_SECRET"
name: "my-app"
env:
- name: "DATABASE_URL"
from:
env: "PRODUCTION_DATABASE_URL"
- name: "API_KEY"
from:
env: "MY_API_KEY"
- name: "SECRET_KEY"
from:
env: "APP_SECRET"
Set these in your shell before deploying:
export PRODUCTION_DATABASE_URL="postgres://prod-db:5432/myapp"
export MY_API_KEY="your-api-key-here"
export APP_SECRET="your-secret-here"
haloy deploy
export PRODUCTION_DATABASE_URL="postgres://prod-db:5432/myapp"
export MY_API_KEY="your-api-key-here"
export APP_SECRET="your-secret-here"
haloy deploy
Tip: You have the option to define environment variables in files which the haloy CLI tool will automatically load. See Environment Files for more details.
From Secret Providers
Use external secret management services like 1Password:
name: "my-app"
secret_providers:
onepassword:
production-db:
vault: "Production"
item: "Database Credentials"
api-keys:
vault: "API Services"
item: "Third-party APIs"
env:
- name: "DATABASE_PASSWORD"
from:
secret: "onepassword:production-db.password"
- name: "API_SECRET"
from:
secret: "onepassword:api-keys.secret-key"
- name: "STRIPE_KEY"
from:
secret: "onepassword:api-keys.stripe-key"
name: "my-app"
secret_providers:
onepassword:
production-db:
vault: "Production"
item: "Database Credentials"
api-keys:
vault: "API Services"
item: "Third-party APIs"
env:
- name: "DATABASE_PASSWORD"
from:
secret: "onepassword:production-db.password"
- name: "API_SECRET"
from:
secret: "onepassword:api-keys.secret-key"
- name: "STRIPE_KEY"
from:
secret: "onepassword:api-keys.stripe-key"
See Secret Providers for more details.
Environment Files
Haloy automatically loads environment variables from these files (in order):
.envin the current directory.env.localin the current directory (overrides.env).env.{target}for target-specific variables.envin the Haloy config directory (~/.config/haloy/)
Example .env File
DATABASE_URL=postgres://localhost:5432/myapp
API_KEY=your-secret-api-key
DEBUG=true
MAX_CONNECTIONS=100
DATABASE_URL=postgres://localhost:5432/myapp
API_KEY=your-secret-api-key
DEBUG=true
MAX_CONNECTIONS=100
Local Overrides with .env.local
The .env.local file is loaded after .env and overrides any duplicate variables. This follows a common convention where:
.envcontains committed defaults and safe-to-share values.env.localcontains local or secret overrides that are gitignored
.env (committed to repository):
DATABASE_URL=postgres://localhost:5432/myapp
API_KEY=placeholder
DEBUG=true
DATABASE_URL=postgres://localhost:5432/myapp
API_KEY=placeholder
DEBUG=true
.env.local (gitignored, local overrides):
API_KEY=your-actual-secret-key
DEBUG=false
API_KEY=your-actual-secret-key
DEBUG=false
In this example, API_KEY and DEBUG from .env.local override the values in .env, while DATABASE_URL remains unchanged.
Target-Specific .env Files
For multi-target deployments:
.env.production:
DATABASE_URL=postgres://prod-db:5432/myapp
DEBUG=false
NODE_ENV=production
DATABASE_URL=postgres://prod-db:5432/myapp
DEBUG=false
NODE_ENV=production
.env.staging:
DATABASE_URL=postgres://staging-db:5432/myapp
DEBUG=true
NODE_ENV=staging
DATABASE_URL=postgres://staging-db:5432/myapp
DEBUG=true
NODE_ENV=staging
Mixed Approaches
Combine different methods in a single configuration:
name: "my-app"
env:
# Plain text for non-sensitive values
- name: "PORT"
value: "8080"
- name: "LOG_LEVEL"
value: "info"
# From environment variables
- name: "DATABASE_URL"
from:
env: "PRODUCTION_DATABASE_URL"
# From secret providers
- name: "API_SECRET"
from:
secret: "onepassword:api-keys.secret"
name: "my-app"
env:
# Plain text for non-sensitive values
- name: "PORT"
value: "8080"
- name: "LOG_LEVEL"
value: "info"
# From environment variables
- name: "DATABASE_URL"
from:
env: "PRODUCTION_DATABASE_URL"
# From secret providers
- name: "API_SECRET"
from:
secret: "onepassword:api-keys.secret"
Build Arguments from Environment Variables
When building Docker images locally, you often need the same variable both as a runtime environment variable and as a build argument. Instead of duplicating the definition, use the build_arg option:
name: "my-app"
env:
- name: "NODE_ENV"
value: "production"
build_arg: true # Also pass as build argument
- name: "API_URL"
from:
env: "API_URL"
build_arg: true
image:
repository: "my-app"
build_config:
context: "."
name: "my-app"
env:
- name: "NODE_ENV"
value: "production"
build_arg: true # Also pass as build argument
- name: "API_URL"
from:
env: "API_URL"
build_arg: true
image:
repository: "my-app"
build_config:
context: "."
This is equivalent to the more verbose:
env:
- name: "NODE_ENV"
value: "production"
- name: "API_URL"
from:
env: "API_URL"
image:
repository: "my-app"
build_config:
context: "."
args:
- name: "NODE_ENV"
value: "production"
- name: "API_URL"
from:
env: "API_URL"
env:
- name: "NODE_ENV"
value: "production"
- name: "API_URL"
from:
env: "API_URL"
image:
repository: "my-app"
build_config:
context: "."
args:
- name: "NODE_ENV"
value: "production"
- name: "API_URL"
from:
env: "API_URL"
Notes:
- The
build_argoption only takes effect when the image has abuild_config(i.e., the image will be built locally) - If the same variable name is explicitly defined in
build_config.args, the explicit definition takes precedence - All value sources work with
build_arg: plainvalue,from.env, andfrom.secret
Multi-Target Environment Variables
Merge environment variables per target:
name: "my-app"
# Base environment variables
env:
- name: "LOG_LEVEL"
value: "info"
- name: "FEATURE_FLAG"
value: "false"
targets:
production:
env: # Merges with base env
- name: "NODE_ENV"
value: "production"
- name: "LOG_LEVEL"
value: "warn" # Overrides base LOG_LEVEL
- name: "DATABASE_URL"
from:
secret: "onepassword:prod-db.url"
# Result: NODE_ENV=production, LOG_LEVEL=warn, FEATURE_FLAG=false, DATABASE_URL=<from secret>
staging:
env: # Merges with base env
- name: "NODE_ENV"
value: "staging"
- name: "LOG_LEVEL"
value: "debug" # Overrides base LOG_LEVEL
- name: "DATABASE_URL"
from:
env: "STAGING_DATABASE_URL"
# Result: NODE_ENV=staging, LOG_LEVEL=debug, FEATURE_FLAG=false, DATABASE_URL=<from env>
name: "my-app"
# Base environment variables
env:
- name: "LOG_LEVEL"
value: "info"
- name: "FEATURE_FLAG"
value: "false"
targets:
production:
env: # Merges with base env
- name: "NODE_ENV"
value: "production"
- name: "LOG_LEVEL"
value: "warn" # Overrides base LOG_LEVEL
- name: "DATABASE_URL"
from:
secret: "onepassword:prod-db.url"
# Result: NODE_ENV=production, LOG_LEVEL=warn, FEATURE_FLAG=false, DATABASE_URL=<from secret>
staging:
env: # Merges with base env
- name: "NODE_ENV"
value: "staging"
- name: "LOG_LEVEL"
value: "debug" # Overrides base LOG_LEVEL
- name: "DATABASE_URL"
from:
env: "STAGING_DATABASE_URL"
# Result: NODE_ENV=staging, LOG_LEVEL=debug, FEATURE_FLAG=false, DATABASE_URL=<from env>
Note: Environment variables defined in targets are merged with base environment variables. Target-specific values override base values with the same name, while other base values are preserved.
Best Practices
- Never commit secrets: Use
.envfiles or secret providers for sensitive data - Add .env files to .gitignore: Prevent accidental commits
echo ".env" >> .gitignore
echo ".env.local" >> .gitignore
echo ".env.*" >> .gitignore
echo ".env" >> .gitignore
echo ".env.local" >> .gitignore
echo ".env.*" >> .gitignore
- Use secret providers for production: More secure than plain text or environment variables
- Document required variables: Keep a
.env.examplefile:
DATABASE_URL=postgres://localhost:5432/myapp
API_KEY=your-api-key-here
SECRET_KEY=your-secret-here
DATABASE_URL=postgres://localhost:5432/myapp
API_KEY=your-api-key-here
SECRET_KEY=your-secret-here
- Validate configuration: Use
haloy validate-configto check your setup
Viewing Resolved Configuration
To see the final configuration with all secrets resolved (use with caution):
haloy validate-config --show-resolved-config
haloy validate-config --show-resolved-config
Warning: This will display all secrets in plain text. Only use in secure environments.