Understanding build secrets vs runtime secrets
Understanding build secrets vs runtime secrets
When deploying applications, it's important to understand the difference between secrets available during the build process and those available when your application runs. This guide explains how each type works and why they're handled differently.
What are build secrets?
Build secrets are environment variables available only during the Docker image building process. These are the secrets you manually add in the "Environment secrets" section of your application settings.
Common uses for build secrets:
- Private package repository tokens (NPM, Composer, etc.)
- API keys needed to download dependencies
- Authentication for private Git submodules
- Build-time configuration values
What are runtime secrets?
Runtime secrets are environment variables available when your application container is running. These include both your manually added secrets and auto-injected service credentials.
Types of runtime secrets:
- User-defined secrets: The same secrets you add manually that were available during build
- Auto-injected service credentials: Automatically generated credentials from your application services (databases, cache, etc.)
Auto-injected service credentials
When you add services like MySQL, PostgreSQL, or Redis to your application, the platform automatically generates secure credentials and injects them as environment variables. You can view these in the "Injected secrets" modal.
Examples of auto-injected variables:
- Database services:
DB_HOST
,DB_PORT
,DB_DATABASE
,DB_USERNAME
,DB_PASSWORD
- Redis/Valkey:
REDIS_HOST
,REDIS_PORT
,REDIS_PASSWORD
- MongoDB:
MONGO_HOST
,MONGO_PORT
,MONGO_DATABASE
,MONGO_USERNAME
,MONGO_PASSWORD
Environment variable expansion
You can now reference other environment variables within your variable values, making configuration more flexible and maintainable. This feature works for both build and runtime secrets.
How variable expansion works:
- Use
${VARIABLE_NAME}
to reference another variable - Use
${VARIABLE_NAME:-default}
to provide a default value if the variable isn't set - Variables can reference both user-defined secrets and auto-injected service credentials
Examples of variable expansion:
DATABASE_URL: mysql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT:-3306}/${DB_DATABASE}
REDIS_URL: redis://${REDIS_HOST:-redis}:${REDIS_PORT:-6379}
API_ENDPOINT: ${API_BASE_URL}/v${API_VERSION:-1}/api
APP_URL: https://${APP_DOMAIN}
Benefits of variable expansion:
- DRY configuration: Define base values once and reuse them
- Environment flexibility: Use defaults that can be overridden
- Simplified URLs: Build complex connection strings from simple parts
- Easier maintenance: Change base values without updating multiple variables
Why service credentials aren't available during build
Auto-injected service credentials are not available during the Docker build process. This is by design for several important reasons:
- Security: Keeping service credentials out of the build process prevents them from being accidentally included in Docker image layers
- Flexibility: Services can be added, removed, or have their credentials rotated without rebuilding your application
- Separation of concerns: Build-time operations shouldn't depend on runtime services
Working with this limitation
If your application needs to run database migrations or perform other operations that require service access, you have several options:
1. Use init containers (recommended)
Configure your application to run migrations or setup tasks when the container starts. Make sure these commands can be ran multiple times because every instance of your application will always execute these commands.
2. Use conditional logic
Make your build scripts check for the presence of credentials:
if [ -n "$DB_HOST" ]; then
php artisan migrate
else
echo "Skipping migrations during build"
fi
Best practices
- Keep build secrets minimal: Only add secrets that are absolutely necessary for the build process
- Use init containers for setup: Run database migrations and other setup tasks when the container starts, not during build
- Never hardcode credentials: Always use environment variables for both build and runtime secrets
- Review injected secrets: Check the "Injected secrets" modal to see what environment variables are automatically available to your application
- Use variable expansion wisely: Build connection strings and complex values from simpler base variables