Skip to content

While you fix the namespaces, let's lock in the Deployment Strategy.

You are absolutely correct to decouple the Engine (the Docker Image) from the Brain (the Configuration).

The "Ticket Masala" strategy is Immutable Binary, Mutable Config.

We do not bake masala_domains.yaml into the Docker image. If we did, we would have to redeploy the container just to change a ticket priority rule. That is unacceptable.

Here is the Reference Deployment Architecture.

  1. The Strategy: "The Brain in a Box"

We build a Single Generic Docker Image (ticket-masala:v3). This image contains the .NET runtime and your compiled code. It knows how to process work, but it doesn't know what the work is until it reads the mounted volumes.

The Container Contract:

/app/config: Read-Only Volume. Contains YAML rules.

/app/data: Read-Write Volume. Contains masala.db (SQLite).

Environment Variables: Secrets only (Connection strings, API Keys).

Code snippet

graph LR subgraph Host Machine HostConfig[./config/ folder] HostData[./data/ folder] end

subgraph Container [Docker Container: ticket-masala:v3]
    AppBin[NET 8 Binaries]
    AppConfig[/app/config]
    AppData[/app/data]

    AppBin -- Reads Rules --> AppConfig
    AppBin -- Reads/Writes DB --> AppData
end

HostConfig -- Volume Mount (ro) --> AppConfig
HostData -- Volume Mount (rw) --> AppData
  1. The Optimized Dockerfile

Do not use the default Visual Studio Dockerfile. It is bloated. We need a streamlined, multi-stage build that enforces our folder structure.

Create Dockerfile in the root (next to .sln): Dockerfile

STAGE 1: Build & Publish

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src

Copy csproj and restore as distinct layers (Caching Strategy)

COPY ["src/TicketMasala.Web/TicketMasala.Web.csproj", "src/TicketMasala.Web/"] RUN dotnet restore "src/TicketMasala.Web/TicketMasala.Web.csproj"

Copy everything else and build

COPY . . WORKDIR "/src/src/TicketMasala.Web" RUN dotnet publish "TicketMasala.Web.csproj" -c Release -o /app/publish /p:UseAppHost=false

STAGE 2: Runtime (The "Lite" Image)

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final WORKDIR /app

create standard mount points

We create them here so permissions can be set correctly

RUN mkdir -p /app/config /app/data

Create a non-root user for security

RUN addgroup --system --gid 1000 masala && \ adduser --system --uid 1000 --ingroup masala --shell /bin/sh masala

Chown the directories so the app can write to Data but only Read config

(Config is technically read-only, but the user needs access to list files)

RUN chown -R masala:masala /app/data && \ chown -R masala:masala /app/config

USER masala

Copy the binary from build stage

COPY --from=build --chown=masala:masala /app/publish .

ENVIRONMENT DEFAULTS

ENV MASALA_CONFIG_PATH="/app/config" ENV MASALA_DB_PATH="/app/data/masala.db" ENV ASPNETCORE_URLS="http://+:8080"

EXPOSE 8080

ENTRYPOINT ["dotnet", "TicketMasala.Web.dll"]

  1. The Orchestration (docker-compose.yml)

This is how you run it locally or on a server. Notice how we map the directories. YAML

services: ticket-masala: image: ticket-masala:latest build: context: . dockerfile: Dockerfile container_name: ticket_masala_engine restart: unless-stopped

# LIMITS: We are "Lite", so we prove it.
# If the engine exceeds 512MB, we have a memory leak in the compiler.
deploy:
  resources:
    limits:
      cpus: '1.0'
      memory: 512M

ports:
  - "8080:8080"

volumes:
  # THE BRAIN: Injecting the rules (Read Only)
  - ./config:/app/config:ro

  # THE MEMORY: Persisting the DB (Read Write)
  - ./data:/app/data:rw

environment:
  # We tell the app explicitly where to look
  - ConnectionStrings__DefaultConnection=Data Source=/app/data/masala.db;Cache=Shared;Mode=ReadWriteCreate
  - MasalaConfig__ConfigPath=/app/config
  - ASPNETCORE_ENVIRONMENT=Production
  1. Advanced: The "Hot Reload" Capability

Since we are mounting the config folder, you have a superpower: Zero-Downtime Reconfiguration.

In your C# ConfigurationService, you will implement a FileSystemWatcher.

Scenario: Marketing wants to change the "Urgent" ticket threshold from $500 to $1000.

Action: You edit config/masala_domains.yaml on the host machine.

Reaction:

    Docker propagates the change to /app/config/masala_domains.yaml.

    The C# FileSystemWatcher detects the Changed event.

    The RuleCompilerService recompiles the Expression Trees in the background.

    New rules apply instantly to the next ticket processed.

    No restart required.
  1. Deployment Guardrails

    SQLite on Docker Volumes:

    CRITICAL: Do not mount the ./data volume on an NFS (Network File System) or Azure Files share. SQLite's locking mechanism (WAL) requires a local disk.
    
    If deploying to cloud (AWS/Azure), use "Block Storage" (EBS/Managed Disk), not "File Storage."
    

    Permissions:

    The ./data folder on your host machine must be writable by the user ID 1000 (defined in the Dockerfile). If you get "Permission Denied" on masala.db, verify host folder permissions: chown -R 1000:1000 ./data.
    

Next Step

Once your Python script finishes the namespace migration, run: Bash

docker compose up --build

If it starts and logs "Listening on port 8080", your architecture is valid. Shall we proceed to implementing the ConfigurationService that reads these mounted YAML files?