Skip to content

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:

  1. UI Development Server Port: Hardcoded to 5175 in vite.config.ts
  2. API URLs: Partially hardcoded with fallbacks to localhost
  3. MCP Server Health Checks: Missing HTTP endpoints for infrastructure monitoring
  4. 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:

  1. Local Development: Direct process execution with localhost endpoints
  2. Docker Compose Local: Containerized with host networking
  3. Docker Compose Production-like: Container networking with service DNS
  4. Azure Container Apps: Cloud deployment with internal and external URLs
  5. 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

  1. Environment Variables First: All configuration values read from environment variables
  2. Sensible Defaults: Provide localhost defaults for development convenience
  3. Build-Time Injection: Support Docker build args for static configuration
  4. Runtime Override: Allow environment-specific overrides without rebuilds
  5. 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)

# Override for host networking
VITE_API_BASE_URL=http://localhost:8000

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

  1. Environment Portability: Same code deploys everywhere
  2. Configuration Flexibility: Change environments without rebuilding
  3. Security: Secrets managed in CI/CD, not code
  4. Testability: Easy to test production builds locally
  5. DevOps Friendly: Standard practices for all platforms
  6. Health Monitoring: Infrastructure tools can monitor MCP servers
  7. Documentation: Clear configuration per environment

Negative

  1. Configuration Complexity: More environment variables to manage
  2. Documentation Burden: Must document all configuration options
  3. Testing Matrix: Need to test multiple environment configurations
  4. Build-Time vs Runtime: Vite embeds some vars at build time (trade-off for SPA performance)

Mitigations

  1. Comprehensive Documentation: Created deployment guides for each environment
  2. Environment Examples: .env.example files document all options
  3. Validation: Scripts verify configuration before startup
  4. 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 .env at 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.ts to use process.env.VITE_PORT
  • Added VITE_PORT to .env and .env.example
  • Updated Dockerfile to accept build args
  • Updated docker-compose.yml with build args
  • Added HTTP /health endpoints 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/
  • 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.