ADR-037: Environment-Based Configuration for Multi-Environment Deployment
Status
Accepted
Date
2025-10-11
Context
During initial deployment testing, we discovered that several configuration values were hardcoded in the application, including:
- UI Development Server Port: Hardcoded to 5175 in
vite.config.ts - API URLs: Partially hardcoded with fallbacks to
localhost - MCP Server Health Checks: Missing HTTP endpoints for infrastructure monitoring
- Docker Configuration: No mechanism to override API URLs at build time
These hardcoded values created several problems:
- Environment Portability: Code that worked in development failed in containers and cloud deployments
- Configuration Rigidity: Required code changes to deploy to different environments
- Testing Limitations: Difficult to test production-like configurations locally
- DevOps Friction: CI/CD pipelines couldn't inject environment-specific values
Deployment Environments
The system must support multiple deployment scenarios:
- Local Development: Direct process execution with
localhostendpoints - Docker Compose Local: Containerized with host networking
- Docker Compose Production-like: Container networking with service DNS
- Azure Container Apps: Cloud deployment with internal and external URLs
- CI/CD Pipelines: Automated builds with environment-specific configuration
Decision
Adopt environment-based configuration throughout the application using environment variables with sensible defaults.
Configuration Strategy
- Environment Variables First: All configuration values read from environment variables
- Sensible Defaults: Provide localhost defaults for development convenience
- Build-Time Injection: Support Docker build args for static configuration
- Runtime Override: Allow environment-specific overrides without rebuilds
- Documentation: Clear guidance for each deployment environment
Implementation
1. UI Configuration
vite.config.ts - Dynamic port and host configuration:
export default defineConfig({
plugins: [react()],
server: {
host: '0.0.0.0',
port: parseInt(process.env.VITE_PORT || '5173'),
strictPort: false,
},
})
Environment Variables:
# apps/ui/.env
VITE_PORT=5173 # Development server port
VITE_API_BASE_URL=http://localhost:8000 # API endpoint (per environment)
Docker Build Args:
# apps/ui/Dockerfile
ARG VITE_API_BASE_URL=http://localhost:8000
ENV VITE_API_BASE_URL=$VITE_API_BASE_URL
RUN npm run build # Embeds env vars at build time
2. API Configuration
FastAPI - Already using environment variables via .env:
# .env (repository root)
APP_CORS_ORIGINS="http://localhost:5173,http://localhost:8080"
MCP_APPLICATION_VERIFICATION_URL=http://localhost:8010/mcp
MCP_DOCUMENT_PROCESSING_URL=http://localhost:8011/mcp
MCP_FINANCIAL_CALCULATIONS_URL=http://localhost:8012/mcp
3. MCP Server Configuration
Server Initialization - Port from environment:
# apps/mcp_servers/*/server.py
mcp.settings.host = MCPServerConfig.get_host() # From env: MCP_SERVER_HOST
mcp.settings.port = MCPServerConfig.get_port("APPLICATION_VERIFICATION", 8010)
Health Check Endpoints - Added HTTP endpoints for infrastructure monitoring:
@mcp.custom_route("/health", methods=["GET"])
async def health_endpoint(request: Request) -> JSONResponse:
return JSONResponse({
"status": "healthy",
"timestamp": datetime.now(timezone.utc).isoformat(),
"server": "application_verification",
"version": "1.0.0",
})
4. Docker Compose Configuration
Build Args for UI:
ui:
build:
context: ./apps/ui
dockerfile: Dockerfile
args:
VITE_API_BASE_URL: ${VITE_API_BASE_URL:-http://localhost:8000}
Environment Variables for Services:
api:
environment:
MCP_APP_VERIFICATION_URL: "http://mcp-app-verification:8010/mcp"
MCP_DOC_PROCESSING_URL: "http://mcp-doc-processing:8011/mcp"
MCP_FINANCIAL_CALC_URL: "http://mcp-financial-calc:8012/mcp"
Configuration Per Environment
Local Development
# Use .env files with localhost
VITE_PORT=5173
VITE_API_BASE_URL=http://localhost:8000
MCP_APPLICATION_VERIFICATION_URL=http://localhost:8010/mcp
Docker Compose (Local)
Docker Compose (Production-like)
# Use container service names
VITE_API_BASE_URL=http://api:8000
MCP_APPLICATION_VERIFICATION_URL=http://mcp-app-verification:8010/mcp
Azure Container Apps
# Use Azure internal URLs
VITE_API_BASE_URL=https://loan-defenders-api.azurecontainerapps.io
MCP_APPLICATION_VERIFICATION_URL=http://mcp-app-verification.internal.env.azurecontainerapps.io/mcp
CI/CD Pipeline
# GitHub Actions / Azure DevOps
- name: Build UI
env:
VITE_API_BASE_URL: ${{ secrets.AZURE_API_URL }}
run: |
docker build \
--build-arg VITE_API_BASE_URL=$VITE_API_BASE_URL \
-t loan-defenders-ui:${{ github.sha }} \
./apps/ui
Rationale
Flexibility Without Code Changes
Environment variables enable the same codebase to run in any environment by simply changing configuration at deployment time.
Security Best Practices
Secrets and environment-specific URLs are managed outside the codebase: - CI/CD secrets for production URLs - Environment-specific configuration files - No sensitive data in version control
Developer Experience
Sensible defaults mean developers can start coding immediately:
- .env.example files document all configuration
- Local development works with zero configuration
- Clear upgrade path to production
Infrastructure Compatibility
Standard environment variables work with all deployment platforms: - Docker and Docker Compose - Kubernetes and Azure Container Apps - Azure App Service - GitHub Actions and Azure DevOps
MCP Best Practices
Custom HTTP health endpoints follow FastMCP official guidance:
"custom_route() allows adding arbitrary HTTP endpoints outside the standard MCP protocol, which can be useful for OAuth callbacks, health checks, or admin APIs."
The MCP protocol's built-in ping is for client-server liveness during active sessions, not infrastructure health monitoring.
Consequences
Positive
- Environment Portability: Same code deploys everywhere
- Configuration Flexibility: Change environments without rebuilding
- Security: Secrets managed in CI/CD, not code
- Testability: Easy to test production builds locally
- DevOps Friendly: Standard practices for all platforms
- Health Monitoring: Infrastructure tools can monitor MCP servers
- Documentation: Clear configuration per environment
Negative
- Configuration Complexity: More environment variables to manage
- Documentation Burden: Must document all configuration options
- Testing Matrix: Need to test multiple environment configurations
- Build-Time vs Runtime: Vite embeds some vars at build time (trade-off for SPA performance)
Mitigations
- Comprehensive Documentation: Created deployment guides for each environment
- Environment Examples:
.env.examplefiles document all options - Validation: Scripts verify configuration before startup
- Defaults: Sensible defaults for development convenience
Alternatives Considered
Alternative 1: Hardcoded per Environment
- Rejected: Requires separate codebases or branches per environment
- Issues: Maintenance nightmare, merge conflicts, deployment errors
Alternative 2: Runtime Configuration File
- Rejected for Vite: Vite bundles environment vars at build time for performance
- Used for API: API loads
.envat runtime (Python allows this) - Trade-off: SPA performance vs deployment flexibility
Alternative 3: Config Service
- Rejected: Over-engineering for current scale
- Future: Consider for 20+ microservices with complex config
Implementation Checklist
- Updated
vite.config.tsto useprocess.env.VITE_PORT - Added
VITE_PORTto.envand.env.example - Updated
Dockerfileto accept build args - Updated
docker-compose.ymlwith build args - Added HTTP
/healthendpoints to all MCP servers - Updated startup scripts to use health endpoints
- Created deployment configuration documentation
- Tested local development (working)
- Test Docker Compose build and deployment
- Test Azure Container Apps deployment
- Update CI/CD pipelines with environment-specific configuration
References
- MCP Python SDK: https://github.com/modelcontextprotocol/python-sdk
- FastMCP Documentation:
mcp.server.fastmcp.server.py- custom_route() usage - Vite Environment Variables: https://vitejs.dev/guide/env-and-mode.html
- Docker Build Args: https://docs.docker.com/engine/reference/builder/#arg
- Azure Container Apps Configuration: https://learn.microsoft.com/azure/container-apps/
Related ADRs
- ADR-009: Azure Container Apps Deployment
- ADR-034: Apps Folder Reorganization
- ADR-035: Container Deployment Strategy
Notes
This ADR documents the shift from hardcoded configuration to environment-based configuration discovered during deployment testing. The changes enable proper multi-environment deployment while maintaining developer experience through sensible defaults.