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.
- 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
- 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"]
- 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
- 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.
-
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?