Skip to main content

Overview

PriceSignal uses ASP.NET Core’s configuration system, which supports multiple configuration sources including appsettings.json, environment variables, and mounted secrets.

Configuration Priority

Configuration is loaded in this order (later sources override earlier ones):
  1. appsettings.json - Base configuration
  2. Environment-specific files - appsettings.{Environment}.json
  3. Environment variables
  4. Mounted secrets (production only)

Environment Variables

Required Variables

# ASP.NET Core Environment
ASPNETCORE_ENVIRONMENT=Development

# Database Connection
ConnectionStrings__PriceSignalDB=Host=localhost;Port=5432;Database=price_signal;Username=postgres;Password=yourpassword

# NATS Message Broker
Nats__Url=nats://localhost:4222

Optional Variables

# Binance Settings (when enabled)
Binance__Enabled=true
Binance__WebsocketUrl=wss://stream.binance.com:9443/stream?streams=btcusdt@aggTrade/ethusdt@aggTrade
Binance__ApiUrl=https://api.binance.com

# Alpaca Settings
Alpaca__ApiUrl=https://data.alpaca.markets

# Logging
Logging__LogLevel__Default=Information
Logging__LogLevel__Microsoft.AspNetCore=Warning
Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning

Configuration Sections

Database Configuration

The database connection method differs between development and production environments.

Development

Uses connection string from appsettings.json or environment variable:
{
  "ConnectionStrings": {
    "PriceSignalDB": "Host=localhost;Port=5432;Database=price_signal;Username=postgres;Password=example"
  }
}
Or as environment variable:
ConnectionStrings__PriceSignalDB="Host=localhost;Port=5432;Database=price_signal;Username=postgres;Password=example"

Production

Reads PostgreSQL URI from /app/secrets/uri file (mounted from Kubernetes secret):
postgresql://username:password@host:5432/price_signal
The application automatically converts this to Npgsql format:
if (isDevelopment)
{
    connectionString = configuration.GetConnectionString("PriceSignalDB");
}
else
{
    connectionString = ConvertToNpgsqlConnectionString(File.ReadAllText("/app/secrets/uri"));
}

NATS Configuration

NATS is used for message passing between services (e.g., Telegram bot notifications):
# Docker development
Nats__Url=nats://host.docker.internal:4222

# Kubernetes production
Nats__Url=nats://nats:4222

# External NATS cluster
Nats__Url=nats://nats-1:4222,nats://nats-2:4222,nats://nats-3:4222

Binance Configuration

Binance integration provides real-time cryptocurrency price feeds:
{
  "Binance": {
    "WebsocketUrl": "wss://stream.binance.com:9443/stream?streams=btcusdt@aggTrade/ethusdt@aggTrade",
    "ApiUrl": "https://api.binance.com",
    "Enabled": "true"
  }
}
When Binance__Enabled=true, the application starts background services for:
  • BinancePairUpdateService - Monitors trading pairs
  • BinancePriceFetcherService - Fetches real-time prices
  • BinanceBackfillService - Backfills historical data
  • BinanceProcessingService - Processes price updates

Alpaca Configuration

Alpaca provides stock and cryptocurrency market data:
{
  "Alpaca": {
    "ApiUrl": "https://data.alpaca.markets"
  }
}
Alpaca credentials are required in production and passed via environment variables or Pulumi secrets. See Infrastructure Configuration.

Logging Configuration

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.EntityFrameworkCore.Database.Command": "Warning"
    }
  }
}
Override with environment variables:
Logging__LogLevel__Default=Debug
Logging__LogLevel__Microsoft.AspNetCore=Information
Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Information

Authentication Configuration

PriceSignal uses Firebase Authentication:
options.Authority = "https://securetoken.google.com/nxtspec";
options.TokenValidationParameters = new TokenValidationParameters
{
    ValidateIssuer = true,
    ValidIssuer = "https://securetoken.google.com/nxtspec",
    ValidateAudience = true,
    ValidAudience = "nxtspec",
    ValidateLifetime = true
};
The Firebase project ID (nxtspec) is hardcoded. To use a different Firebase project, you’ll need to modify the authentication configuration in Program.cs:75-83.

Docker Configuration

Docker Compose Environment

services:
  server:
    image: nayth/price-signal-graph:latest
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - Nats__Url=nats://host.docker.internal:4222
      - Binance__Enabled=false
    ports:
      - 8080:8080

Using .env File

Create a .env file in the same directory as compose.yaml:
# .env
ASPNETCORE_ENVIRONMENT=Development
NATS_URL=nats://host.docker.internal:4222
BINANCE_ENABLED=false

# For production
ALPACA_API_KEY=your_key_here
ALPACA_API_SECRET=your_secret_here
DB_USER=postgres
DB_PASSWORD=strong_password_here
Reference in compose.yaml:
services:
  server:
    environment:
      - ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT}
      - Nats__Url=${NATS_URL}
      - Binance__Enabled=${BINANCE_ENABLED}
      - Alpaca__ApiKey=${ALPACA_API_KEY}
      - Alpaca__ApiSecret=${ALPACA_API_SECRET}

Kubernetes Configuration

ConfigMap for appsettings.json

The Pulumi deployment creates a ConfigMap from the application’s appsettings.json:
var webserverconfig = new ConfigMap("appsettings", new()
{
    Metadata = new ObjectMetaArgs
    {
        Namespace = webserverNs.Metadata.Apply(m => m.Name),
    },
    Data = 
    {
        { "appsettings.json", File.ReadAllText("../src/PriceSignal/appsettings.json") },
    },
});
This is mounted at /app/appsettings.json in the container.

Environment Variables from Pulumi Config

Sensitive values are injected as environment variables:
var env = new[]
{
    new EnvVarArgs()  
    {
        Name = "Alpaca__ApiKey",
        Value = config.RequireSecret("alpacaApiKey"),
    },
    new EnvVarArgs()  
    {
        Name = "Alpaca__ApiSecret",
        Value = config.RequireSecret("alpacaApiSecret"),
    },
    new EnvVarArgs()
    {
        Name = "Nats__Url",
        Value = config.require("natsUrl"),
    },
};

Secrets Volume Mount

Database credentials are mounted from a Kubernetes secret:
volumeMounts:
  - mountPath: /app/secrets
    name: price-signal-secret-volume
    readOnly: true

volumes:
  - name: price-signal-secret-volume
    secret:
      secretName: timescale-cluster-app

Frontend Configuration

The React frontend is built with a configured WebSocket URL:

Build-time Configuration

FROM node:21 AS build-node
WORKDIR /app
COPY src/react-app/ ./
ENV VITE_WS_URL=wss://price-signal-graph.nxtspec.com/graphql
RUN npm install && npm run build

Custom WebSocket URL

Build with a custom URL:
docker build \
  --build-arg VITE_WS_URL=wss://your-domain.com/graphql \
  -t price-signal:custom .

Configuration Best Practices

Never commit sensitive credentials to version control. Use environment variables, secrets managers, or encrypted Pulumi configs.

Development

  1. Use appsettings.Development.json for local overrides
  2. Keep a .env.example file with dummy values
  3. Use host.docker.internal for Docker-to-host connections
  4. Enable detailed logging for debugging

Production

  1. Use environment-specific appsettings.Production.json
  2. Store secrets in Kubernetes Secrets or secret managers
  3. Use encrypted Pulumi config for sensitive values
  4. Enable only necessary logging to reduce noise
  5. Use strong, randomly generated passwords
  6. Restrict network access to databases and internal services

Validation

Check Configuration at Runtime

# View environment variables in container
docker compose exec server env | sort

# Check appsettings.json
docker compose exec server cat /app/appsettings.json

# Kubernetes pod
kubectl exec -it <pod-name> -- env | sort
kubectl exec -it <pod-name> -- cat /app/appsettings.json

Test Database Connection

# From application container
docker compose exec server dotnet PriceSignal.dll --check-db

# Direct PostgreSQL test
psql "$ConnectionStrings__PriceSignalDB" -c "SELECT version();"

Next Steps

Build docs developers (and LLMs) love