How TeslaMate Manages Environment Configuration Across Dev, Test, and Production
TeslaMate implements a three-layer environment configuration system using compile-time base settings in config/config.exs, environment-specific overrides in config/dev.exs, config/test.exs, and config/prod.exs, and runtime variable resolution in config/runtime.exs that reads system environment variables at startup.
TeslaMate, a self-hosted data logger for Tesla vehicles built on the Elixir/Phoenix framework, relies on a sophisticated environment configuration strategy to maintain distinct behaviors across development, testing, and production deployments. The system separates compile-time defaults from runtime secrets, allowing operators to deploy the same compiled release across different environments simply by adjusting environment variables. This approach ensures that database credentials, API keys, and host-specific settings remain external to the codebase while providing optimized settings for each stage of the development lifecycle.
Configuration Architecture Overview
TeslaMate’s configuration follows a strict loading order that combines three distinct layers. First, the base configuration establishes common defaults for all environments. Second, environment-specific files override these defaults based on the MIX_ENV value. Third, the runtime configuration executes after the application starts, fetching final values from the system environment using System.get_env/2 and Util.fetch_env!/2 functions.
This layered approach means that settings in config/runtime.exs take precedence over compile-time configurations in config/prod.exs or config/dev.exs, enabling dynamic configuration without recompilation.
Base Configuration Layer
The foundation of TeslaMate’s configuration resides in config/config.exs. This file defines settings shared across all environments, including the repository list, endpoint defaults, logger formatting, and gettext locale configuration.
Crucially, this file delegates to environment-specific configurations using the dynamic import statement:
import_config "#{config_env()}.exs"
The config_env/0 function returns the current environment atom (:dev, :test, or :prod), ensuring that the appropriate override file loads immediately after the base settings are established.
Environment-Specific Overrides
Each deployment stage has a dedicated configuration file that tailors the application behavior for its specific context.
Development Configuration (config/dev.exs)
The development environment prioritizes developer experience and debugging capabilities. According to the TeslaMate source code, config/dev.exs enables code reloading for rapid iteration, configures detailed error pages with stack traces, and sets up live-reload patterns for the Phoenix endpoint. The HTTP server binds to port 4000, and the logger outputs debug-level information to assist with troubleshooting.
Test Configuration (config/test.exs)
For the test environment, TeslaMate minimizes external dependencies and isolates database interactions. The config/test.exs file explicitly disables the HTTP server to prevent port conflicts during parallel test runs. It configures the Ecto repository to use the sandbox pool, ensuring database transactions roll back after each test. Additionally, the logger level is set to :warning to reduce noise during test execution.
Production Configuration (config/prod.exs)
Production settings in config/prod.exs focus on performance and security. The configuration forces the web server to start with server: true, points to a pre-compiled static asset manifest for efficient file serving, and configures a minimal logger suitable for containerized deployments. Unlike development, code reloading is strictly disabled, and error pages present user-friendly messages without revealing implementation details.
Runtime Configuration Layer
Executed after compilation, config/runtime.exs resolves final configuration values from environment variables. This file is critical for containerized deployments, allowing operators to inject secrets and host-specific settings without rebuilding the application.
Database Connection Settings
TeslaMate constructs database connections from individual environment variables rather than a single connection string. As implemented in lines 89-122 of config/runtime.exs, the system reads:
DATABASE_USER,DATABASE_PASS,DATABASE_HOST,DATABASE_NAME,DATABASE_PORTfor basic connectivityDATABASE_POOL_SIZEandDATABASE_TIMEOUTfor connection management- Optional SSL settings for encrypted database connections
These variables include sensible defaults for local development but require explicit values in production.
HTTP Binding and Endpoint Security
The HTTP server configuration utilizes Util.choose_http_binding_address/0 to parse HTTP_BINDING_ADDRESS and PORT environment variables (lines 52-86). This utility function determines whether to bind to a specific IP address, a Unix socket, or the default IPv6 socket.
Security-sensitive endpoint settings are configured in lines 55-66, reading VIRTUAL_HOST, URL_PATH, SECRET_KEY_BASE, and SIGNING_SALT from the environment. When these are not provided, the system falls back to cryptographically secure random strings generated at runtime.
MQTT and Auxiliary Services
When DISABLE_MQTT is not set to "true" (and outside of test environments), TeslaMate initializes the MQTT client using environment variables for host, port, credentials, TLS flags, and namespace (lines 68-78).
Additional optional configurations include IMPORT_DIR for bulk data imports and SRTM_CACHE for elevation data caching (lines 80-88).
Practical Configuration Examples
Running TeslaMate in Development
To start the application locally with hot reloading and debug output:
export DATABASE_USER=postgres
export DATABASE_PASS=postgres
export DATABASE_HOST=localhost
export SECRET_KEY_BASE=$(openssl rand -base64 48)
mix deps.get
mix ecto.setup
MIX_ENV=dev mix phx.server
Executing the Test Suite
The test environment loads automatically when MIX_ENV is set to test:
MIX_ENV=test mix test
This configuration uses the sandbox database pool and suppresses the HTTP server to ensure isolated, fast test execution.
Deploying Production Containers
For Docker deployments, pass configuration via environment variables:
docker run -e DATABASE_URL=postgres://user:pass@db/teslamate \
-e SECRET_KEY_BASE=$(openssl rand -base64 48) \
-e VIRTUAL_HOST=teslamate.example.com \
-e PORT=4000 \
-e MQTT_HOST=mqtt.example.com \
-e MQTT_NAMESPACE=teslamate \
-p 4000:4000 \
teslamate/teslamate:latest
Summary
- Three-layer architecture: TeslaMate combines base settings (
config/config.exs), environment overrides (config/{dev,test,prod}.exs), and runtime resolution (config/runtime.exs). - Compile-time vs. runtime: Values in
config/runtime.exsoverride compile-time settings, enabling dynamic configuration without recompilation. - Development features: Code reloading, detailed errors, and debug logging optimize the local development experience.
- Test isolation: The test environment disables the HTTP server and uses Ecto sandbox mode for database transaction rollback.
- Production hardening: Pre-compiled assets, minimal logging, and mandatory environment variables for secrets ensure secure deployments.
- Database flexibility: Individual connection parameters (
DATABASE_HOST,DATABASE_PORT, etc.) are read at runtime with sensible development defaults.
Frequently Asked Questions
How do I switch between development and production configurations in TeslaMate?
Set the MIX_ENV environment variable to dev, test, or prod before starting the application. This variable determines which environment-specific file (config/dev.exs, config/test.exs, or config/prod.exs) gets loaded after the base configuration. For production Docker deployments, the image typically defaults to MIX_ENV=prod, while local development uses MIX_ENV=dev.
Can I change database settings without recompiling the TeslaMate release?
Yes. Because config/runtime.exs executes at application startup using System.get_env/2, you can modify DATABASE_USER, DATABASE_PASS, DATABASE_HOST, and other connection variables without rebuilding the release. Simply restart the container or process with the new environment variables, and the runtime configuration will pick up the changes immediately.
What is the difference between compile-time and runtime configuration in TeslaMate?
Compile-time configuration, found in config/config.exs and the environment-specific files, is evaluated when the application is built and baked into the release. Runtime configuration, defined in config/runtime.exs, executes when the application starts, reading values from the system environment. This separation allows sensitive data like SECRET_KEY_BASE to remain outside the compiled artifact while ensuring deployment-specific settings can be adjusted post-build.
How does TeslaMate handle sensitive secrets like SECRET_KEY_BASE?
The application expects SECRET_KEY_BASE and SIGNING_SALT as environment variables read by config/runtime.exs (lines 55-66). If these are not provided, TeslaMate generates cryptographically secure random strings at runtime. However, for production stability—especially in clustered deployments—you should explicitly set these variables to ensure session continuity across restarts and nodes.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →