Skip to main content

1. Installation Manual

The platform is a .NET 9 CQRS/MediatR microservice system with an Ocelot API Gateway, PostgreSQL databases, Kafka event messaging, and a Vite React web application. This guide covers:

  • Windows Server deployment with IIS.
  • IIS reverse proxy and application pool configuration.
  • Kafka and Zookeeper configuration.
  • .NET service configuration.
  • Required software packages.
  • Linux deployment with Docker, Nginx load balancer, and Portainer.
  • Docker image build, push, and stack deployment.
  • Beginner-friendly verification checkpoints.

1.0 Visual Deployment Overview

Use this diagram as the first visual in the installation manual. It shows how users enter the system and how the .NET 9 CQRS/MediatR services, Ocelot API Gateway, PostgreSQL, Kafka, and web frontend fit together.

100%
50%300%
TeraSupport visual deployment overview diagram

Editable source: Visual Deployment Overview.drawio

1.0.1 Deployment Learning Path

Follow this order when deploying TeraSupport. The Windows path is for IIS hosting. The Linux path is for Docker and Portainer hosting.

StepWindows / IIS PathLinux / Docker / Portainer Path
1Prepare Windows Server, enable IIS, and install IIS management tools.Prepare Ubuntu Server and install Docker Engine.
2Install URL Rewrite, Application Request Routing, .NET Hosting Bundle, Node.js, Yarn, PostgreSQL, and Kafka if Kafka is hosted on Windows.Install Portainer CE and create the shared Docker network.
3Publish .NET services and build the React/Vite web app.Build Docker images and push them to the selected registry if needed.
4Create IIS app pools, sites, bindings, environment variables, and rewrite rules.Deploy PostgreSQL, Kafka, and application stacks through Portainer.
5Verify IIS, API Gateway, Swagger, login, ticket creation, mail/Kafka, file upload, and reports.Verify containers, Nginx routes, public URL, Swagger, ticket workflow, mail/Kafka, and reports.

1.0.2 Windows Visual Walkthrough

Use these cards as the beginner deployment checklist for Windows Server and IIS.

STEP WIN-01Enable IIS Role
Server Manager → Add roles and features → Web Server (IIS)

Open Server Manager from the Windows Start menu. Select Manage, then Add Roles and Features. Choose Role-based or feature-based installation, select the local server, and enable Web Server (IIS).

  • Enable: Web Server, Management Tools, and IIS Management Console.
  • Under Web Server, include Common HTTP Features, Health and Diagnostics, Security, and Application Development.
  • Verify: open Internet Information Services (IIS) Manager from Start.
STEP WIN-02Install Rewrite and ARR
Install IIS URL Rewrite and Application Request Routing

Download and install IIS URL Rewrite, then install Application Request Routing 3.0. Open IIS Manager, select the server node, open Application Request Routing Cache, then click Server Proxy Settings.

  • Check Enable proxy.
  • Click Apply in the right Actions panel.
  • Verify: URL Rewrite appears inside the IIS feature list.
STEP WIN-03Install .NET 9 Runtime
Install ASP.NET Core Hosting Bundle for IIS

Download the .NET 9 Hosting Bundle from the official .NET 9 download page. Run the installer as Administrator after IIS is installed.

  • Run: iisreset
  • Verify: dotnet --list-runtimes
  • If IIS was installed after the Hosting Bundle, repair or rerun the Hosting Bundle installer.
STEP WIN-04Prepare Databases
Install PostgreSQL and create service databases

Install PostgreSQL using the official PostgreSQL Windows installer. The installer can include pgAdmin. If pgAdmin is needed separately, use the pgAdmin Windows download.

  • Create: TSUserDB, TSTicketDb, TSCRMDb, TSCallCenterDb, TSDeviceDB, and TSMailDB.
  • Create a production database user with a strong password.
  • Verify: connect using pgAdmin or psql -h localhost -U <user> -d TSTicketDb.
STEP WIN-05Configure Kafka
Use Linux/Docker Kafka or install Kafka manually on Windows

Recommended: run Kafka through the Linux Docker stack and point Windows services to that broker. If Kafka must run on Windows, install Java 17+, download Kafka from Apache Kafka Quickstart, extract it to C:\kafka, then start the broker.

  • Set service variables: KAFKA_HOST=<kafka-host> and KAFKA_PORT=9092.
  • Create required topics if auto-create is disabled.
  • Verify: kafka-topics.bat --bootstrap-server localhost:9092 --list.
STEP WIN-06Publish Services
Publish each .NET microservice in Release mode

From the solution root, restore, build, and publish ApiGateway, User, Ticketing, CRM, CallCenter, DeviceManagement, Mail, and Meta into separate folders under C:\inetpub\terasupport.

  • Run: dotnet restore TS.sln
  • Run: dotnet build TS.sln -c Release
  • Verify: each publish folder contains the service DLL and generated web.config.
STEP WIN-07Build Web App
Install Node.js, install Yarn, and build React/Vite

Install Node.js LTS. Then open PowerShell as Administrator and install Yarn if the project uses Yarn.

  • Run: npm install --global yarn
  • Run inside Web: yarn install and yarn build
  • Copy Web/dist output to C:\inetpub\terasupport\web.
STEP WIN-08Create App Pools
IIS Manager → Application Pools → Add Application Pool

Create one app pool for each service and one for the web app. Use No Managed Code because ASP.NET Core runs out-of-process through the hosting module.

  • Set Start Mode to AlwaysRunning.
  • Set Idle Time-out (minutes) to 0.
  • Verify: each app pool is started before browsing the site.
STEP WIN-09Bind Sites and Ports
IIS Manager → Sites → Add Website / Add Application

Create the public web site first, then add private sites or applications for API Gateway and services. Keep backend service ports private to the server when possible.

  • Bind the web app to HTTPS with the production hostname.
  • Bind API Gateway to a private localhost port or internal host.
  • Verify: the selected certificate matches the public DNS name.
STEP WIN-10Set Environment Variables
IIS Manager → Application → Configuration Editor

Select the IIS application, open Configuration Editor, choose system.webServer/aspNetCore, and add environment variables under environmentVariables.

  • Set DB connection, JWT secret, Kafka host/port, Ocelot downstream hosts, and service base URLs.
  • Use double underscores for nested .NET settings, for example CONNECTIONSTRINGS__DBLOCATION.
  • Recycle the app pool after every change.
STEP WIN-11Configure Rewrite Rules
Web root → web.config → URL Rewrite rules

Add reverse proxy rules so the React web site forwards /ts-api, /files, and /swagger to the API Gateway. Put the React SPA fallback rule after API rules.

  • Verify: https://<host>/ts-api/swagger opens the gateway Swagger page.
  • Verify: refreshing a React route does not produce a 404.
  • Restart IIS with iisreset after rule changes if needed.
STEP WIN-12Verify Application
Verify login, API Gateway, ticket flow, mail events, and reports

Open the public web URL, sign in, create a test ticket, assign it, upload a small attachment, and export a report.

  • Check Windows Event Viewer and IIS logs if a service fails.
  • Confirm Kafka/mail logs show message processing.
  • Confirm no app pool is repeatedly stopping or recycling.

1.0.3 Linux / Portainer Visual Walkthrough

Use these cards as the beginner deployment checklist for Linux, Docker, and Portainer.

STEP LNX-01Prepare Server
Install Ubuntu Server and Docker Engine

Use Ubuntu Server 22.04 LTS or 24.04 LTS. Follow the official Docker Engine on Ubuntu instructions to add Docker's apt repository and install Docker Engine.

  • Run: sudo apt update && sudo apt upgrade -y
  • Install: docker-ce, docker-ce-cli, containerd.io, docker-buildx-plugin, and docker-compose-plugin.
  • Verify: sudo docker run hello-world.
STEP LNX-02Create Docker Network
Create the shared Docker network used by all stacks

The TeraSupport compose files expect all application, database, Kafka, and Nginx containers to share the same Docker network.

  • Run: docker network create ts-network
  • Verify: docker network ls
  • If it already exists, Docker will report that the network exists; continue deployment.
STEP LNX-03Install Portainer
Run Portainer CE container and create admin account

Follow the official Portainer CE install guide. Create the Portainer data volume and run the Portainer CE container.

  • Open: https://<server-ip>:9443
  • Create the first admin user.
  • Select the local Docker environment.
STEP LNX-04Build Images
Build API Gateway, services, web, and Nginx images

From the TeraSupport solution root, build each Dockerfile with the same image names used in production compose files.

  • Run: docker build -t arcapps/terasupport-api-gateway:latest -f ApiGateway/Dockerfile .
  • Repeat for User, Ticketing, CRM, CallCenter, DeviceManagement, Mail, Meta, Web, and Nginx.
  • Verify: docker images | grep terasupport.
STEP LNX-05Deploy PostgreSQL Stack
Portainer → Stacks → Add stack → prod.postgres.compose.yml

In Portainer, open Stacks, click Add stack, name it terasupport-db, paste prod.postgres.compose.yml, replace all sample passwords, and deploy.

  • Verify: all PostgreSQL containers are running.
  • Verify: persistent volumes exist for each database.
  • Do not expose database ports publicly unless explicitly required.
STEP LNX-06Deploy Kafka Stack
Portainer → Stacks → Add stack → prod.kafka.compose.yml

Create a second stack named terasupport-kafka from prod.kafka.compose.yml. The repository stack uses Zookeeper, Kafka, and Kafdrop.

  • Verify app containers use broker kafka:9092.
  • Verify host tools use <server-ip>:29092 if exposed.
  • Open Kafdrop at http://<server-ip>:9001 if that port is enabled.
STEP LNX-07Deploy App Stack
Portainer → Stacks → Add stack → prod.compose.yml

Create the application stack after database and Kafka stacks are healthy. Paste prod.compose.yml, update image tags, hostnames, secrets, and public URLs, then deploy.

  • Verify: ApiGateway, Web, Nginx, User, Ticketing, CRM, CallCenter, DeviceManagement, Mail, and Meta containers are running.
  • Check logs for database connection or Kafka connection errors.
  • Restart only the affected container after fixing variables.
STEP LNX-08Set Stack Variables
Set DB, Kafka, JWT, Ocelot, and service URL variables

Use production values for every secret and endpoint. For Docker, downstream service hostnames should normally be container names such as user, ticketing, crm, and mail.

  • Set KAFKA_HOST=kafka and KAFKA_PORT=9092.
  • Set each CONNECTIONSTRINGS__DBLOCATION to the correct PostgreSQL container and database.
  • Do not leave sample JWT, database, SMTP, or Facebook/Meta secrets in production.
STEP LNX-09Configure Load Balancer
Configure Nginx routes and public entry point

Use the repository Nginx/default.conf for container routing, or configure host Nginx/external load balancer to point to the published Nginx port.

  • Route / to the web container.
  • Route /ts-api, /files, and /swagger to API Gateway.
  • Verify upload size and proxy headers are configured for attachments.
STEP LNX-10Verify Public HTTPS
Open the public URL and verify certificate, login, and routing

Configure DNS to point to the load balancer or server IP. Install the TLS certificate using the chosen proxy/load-balancer method.

  • Verify: https://<domain> opens the login page.
  • Verify: HTTP redirects to HTTPS if required.
  • Verify: browser certificate matches the production domain.
STEP LNX-11Verify APIs and Reports
Run end-to-end application verification

Open Swagger, sign in, create a ticket, assign it, send or receive a message, upload an attachment, and export a report.

  • Check docker logs <container> for service startup errors.
  • Confirm Kafka and Mail logs show expected events.
  • Confirm reports and downloads work through the public URL.

1.1 Application Components

ComponentProjectRuntimePurpose
API GatewayApiGateway/ApiGateway.csproj.NET 9Ocelot reverse gateway for all backend APIs.
User ServiceUser/User.csproj.NET 9Authentication, users, roles, organizations, contacts, regions.
Ticketing ServiceTicketing/Ticketing.csproj.NET 9Tickets, categories, assignment, messages, reports, attachments.
CRM ServiceCRM/CRM.csproj.NET 9CRM and deal/mail workflows.
Call Center ServiceCallCenter/CallCenter.csproj.NET 9Call center module.
Device Management ServiceDeviceManagement/DeviceManagement.csproj.NET 9Devices, RDP/device integrations, device tracking.
Mail ServiceMail/Mail.csproj.NET 9Mail ingestion, SMTP dispatch, Kafka mail workflows.
Meta ServiceMeta/Meta.csproj.NET 9Social/meta integration features.
Web AppWeb/Node 20, Vite, React 19User interface served by IIS or Nginx.
NginxNginx/default.confNginxLinux container load balancer/reverse proxy.

Supporting projects:

ProjectPurpose
UtilitiesShared helper classes, Kafka topics/groups, response utilities.
Shared.AuthorizationShared authorization/JWT support for services.
Authorization.SharedShared authorization package.

1.2 Required Packages and Tools

1.2.1 Windows Server Packages

Install these on the Windows Server:

PackageRequired Version / NotesOfficial Link
Windows Server2019 or later recommended.Vendor-provided OS media/license
IISWeb Server role with Management Console.Installed from Server Manager
.NET Hosting Bundle.NET 9 Hosting Bundle for IIS hosting..NET 9 downloads
.NET SDK.NET 9 SDK if building on the server..NET 9 downloads
ASP.NET Core RuntimeIncluded with Hosting Bundle.ASP.NET Core IIS hosting guide
URL RewriteIIS URL Rewrite module.IIS URL Rewrite
Application Request RoutingIIS ARR module for reverse proxy.IIS Application Request Routing
Node.jsNode LTS for building the web app.Node.js downloads
YarnRequired by the Web/Dockerfile and web project lockfile.Yarn installation
PostgreSQLPostgreSQL 17 recommended, or a managed PostgreSQL server.PostgreSQL Windows installer
pgAdminOptional GUI for PostgreSQL administration.pgAdmin Windows download
KafkaApache Kafka or Confluent Platform 7.4 compatible broker.Apache Kafka Quickstart
GitRequired if pulling source code directly on the server.Git for Windows
NSSM or Windows Service wrapperOptional, only if running services outside IIS.Use only if IIS is not used for service hosting

1.2.2 Linux Packages

Install these on the Linux host:

PackageRequired Version / NotesOfficial Link
Ubuntu Server22.04 LTS or 24.04 LTS recommended.Ubuntu Server
Docker EngineCurrent stable release.Docker Engine on Ubuntu
Docker Compose pluginRequired for local compose validation/build.Included in Docker package instructions
Portainer CEUsed for stack deployment and visual operations.Portainer CE install
GitRequired for pulling source code.Install through apt
Nginx / Traefik / Cloudflare TunnelPublic load-balancing or reverse-proxy option.Nginx load balancing
PostgreSQL containerExisting compose uses postgres:17.5.PostgreSQL Docker image
Kafka/Zookeeper containersExisting compose uses Confluent Platform 7.4.0.Apache Kafka Quickstart
Node.jsOptional on host if building web outside Docker.Node.js downloads
.NET 9 SDKOptional on host if building services outside Docker..NET 9 downloads

1.3 Database Layout

The solution uses PostgreSQL and separates databases by service domain.

ServiceDatabaseDefault Docker Host
UserTSUserDBterasupport-db_postgres_user
Call CenterTSCallCenterDbterasupport-db_postgres_callcenter
CRMTSCRMDbterasupport-db_postgres_crm
Device ManagementTSDeviceDBterasupport-db_postgres_device
MailTSMailDBterasupport-db_postgres_mail
TicketingTSTicketDbterasupport-db_postgres_ticket

The repository includes:

Docker/db-compose.yml
Docker/init-scripts/create-dbs.sh
prod.postgres.compose.yml

For production, use a strong database password and rotate any existing sample credentials before deployment.


1.4 Environment Variables

The services use .NET configuration binding, so nested settings are supplied with double underscores.

1.4.1 Common Variables

VariableExampleUsed By
ASPNETCORE_ENVIRONMENTProductionAll .NET services
ALLOWEDHOSTS* or production hostAll .NET services
LOGGING__LOGLEVEL__DEFAULTInformationAll .NET services
LOGGING__LOGLEVEL__MICROSOFT_ASPNETCOREWarningAll .NET services
CONNECTIONSTRINGS__DBLOCATIONHost=db;Port=5432;Database=TSUserDB;Username=...;Password=...Services with EF Core
JWT__KEYProduction secretAPI Gateway, Ticketing
KAFKA_HOSTkafka or server hostnameUser, Ticketing, CRM, Mail
KAFKA_PORT9092 inside Docker, 29092 host accessUser, Ticketing, CRM, Mail

1.4.2 API Gateway Variables

VariableExample
OCELOTVARIABLES__DOWNSTREAMSCHEMEhttp for Docker, https for IIS local HTTPS
OCELOTVARIABLES__USERMANAGEMENTSERVICEHOSTuser or localhost
OCELOTVARIABLES__USERMANAGEMENTSERVICEPORT8080 or 7250
OCELOTVARIABLES__TICKETINGSYSTEMSERVICEHOSTticketing or localhost
OCELOTVARIABLES__TICKETINGSYSTEMSERVICEPORT8080 or 7137
OCELOTVARIABLES__CRMSERVICEHOSTcrm or localhost
OCELOTVARIABLES__CRMSERVICEPORT8080 or 7126
OCELOTVARIABLES__CALLCENTERHOSTcallcenter or localhost
OCELOTVARIABLES__CALLCENTERPORT8080 or 5042
OCELOTVARIABLES__DEVICEHOSTdevicemanagement or localhost
OCELOTVARIABLES__DEVICEPORT8080 or 7094
OCELOTVARIABLES__MAILSERVICEHOSTmail or localhost
OCELOTVARIABLES__MAILSERVICEPORT8080 or 7122
OCELOTVARIABLES__METASERVICEHOSTmeta or localhost
OCELOTVARIABLES__METASERVICEPORT8080 or 5121

1.4.3 Service-Specific Variables

ServiceVariables
UserMAILAPI__BASEURL, KAFKA_HOST, KAFKA_PORT, CONNECTIONSTRINGS__DBLOCATION
TicketingFILESETTING__FILEBASEURL, USERSERVICE__BASEURL, APISETTINGS__BASEURL, CRMSETTINGS__BASEURL, JWT__KEY, KAFKA_HOST, KAFKA_PORT, AISETTINGS__...
CRMBASEAPISETTINGS__USERSERVICE, FILESETTING__FILEBASEURL, KAFKA_HOST, KAFKA_PORT
Device ManagementREMOTEDEVICESETTINGS__BASEURL, REMOTEDEVICESETTINGS__AUTHTOKEN, APISETTINGS__BASEURL, APISETTINGS__TICKETAPIURL
MailDB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD, KAFKA_HOST, KAFKA_PORT, TicketHTTP_HOST
MetaFACEBOOK__..., INSTAGRAMS__..., SERVERBASE__URL, BASEAPISETTINGS__USERSERVICE, FILESETTING__FILEBASEURL
WebVITE_API_URL, VITE_OLLAMA_BASE_URL, VITE_OLLAMA_PROXY_TARGET, VITE_OLLAMA_DEFAULT_MODEL

Do not copy development .env secrets directly into production. Replace all tokens, passwords, JWT keys, remote-device tokens, and social API credentials.


1.5 Development and Production Ports

1.5.1 Development Ports From launchSettings.json

ServiceHTTPHTTPS
API Gateway52567062
User51847250
Ticketing51937137
CRM52657126
Call Center50427012
Device Management50287094
Mail50707122
Meta51217247

1.5.2 Docker Runtime Ports

Each .NET Dockerfile exposes internal port 8080. Nginx publishes the external web/load-balancer port.

ContainerInternal PortPublic Exposure
apigateway8080Internal, through Nginx
user8080Internal
ticketing8080Internal
crm8080Internal
callcenter8080Internal
devicemanagement8080Internal
mail8080Internal
meta8080Internal
web80Internal, through Nginx
nginx80Public mapped port, current compose uses 3698:80
kafka9092 / 29092Internal Docker / external host access
kafdrop9000Current compose maps host 9001

1.6 Kafka Configuration

The repository includes a Kafka stack:

prod.kafka.compose.yml

It defines:

ContainerImagePurpose
zookeeperconfluentinc/cp-zookeeper:7.4.0Kafka coordination.
kafkaconfluentinc/cp-kafka:7.4.0Event broker.
kafdropobsidiandynamics/kafdropWeb UI for topic inspection.

Kafka listeners:

ListenerAddressUse
Internal Dockerkafka:9092Used by containers.
Host accesslocalhost:29092Used by local Visual Studio or host services.

1.6.1 Kafka Event Flow Diagram

100%
50%300%
Kafka event flow diagram

Editable source: KafkaEventFlowDiagram.drawio

1.6.2 Kafka Topics

Topic constants are defined in Utilities/kafka/KafkaTopics.cs.

Topic
mail-config-topic
ticket-create-topic
mail-fetch-topic
mail-ticket-topic
mail-message-topic
email-send-topic
message-create-topic
custom-email-send-topic
deal-mail-topic
update-deal-mail-topic
get-ticket-status-topic
create-user-request-topic
user-request-update-topic
deal-ticket-topic
mail-deal-topic
deal-mail-notification-topic

1.6.3 Kafka Consumer Groups

Consumer groups are defined in Utilities/kafka/KafkaGroups.cs.

Group
mail-sent-group
mail-config-group
mail-fetch-group
ticket-message-group
ticket-conversion-group
email-send-group
message-create-group
custom-email-send-group
deal-mail-group
update-deal-mail-group
get-ticket-status-group
create-user-request-group
update-user-request-group
create-mail-deal-group
deal-mail-notification-group

1.6.4 Kafka Verification Checklist

Before connecting TeraSupport services to Kafka, verify:

CheckHow to Verify
Broker is reachableRun kafka-topics --bootstrap-server <host>:<port> --list or open Kafdrop.
Required topics existConfirm the topics listed in section 1.6.2 are present, or confirm auto-topic creation is enabled.
Consumer groups appearTrigger a test ticket/message event and confirm consumer groups appear in Kafdrop or Kafka CLI output.
Application configuration is correctConfirm KAFKA_HOST and KAFKA_PORT are set for User, Ticketing, CRM, and Mail services.
Service logs are cleanCheck service logs for successful producer/consumer startup and no repeated broker connection errors.

1.7 Windows Server Deployment With IIS

1.7.0 Windows / IIS Deployment Flow

This diagram shows the Windows deployment sequence and the main IIS routing relationship.

100%
50%300%
Windows Server IIS deployment flow diagram

Editable source: IIS Deployment Flow.drawio

1.7.1 Windows Server Preparation

  1. Sign in to the Windows Server using an administrator account.
  2. Install all Windows updates.
  3. Open Server Manager.
  4. Select Add roles and features.
  5. Enable Web Server (IIS).
  6. Enable IIS role services:
    • Web Server
    • Common HTTP Features
    • Static Content
    • Default Document
    • HTTP Errors
    • Health and Diagnostics
    • HTTP Logging
    • Security
    • Request Filtering
    • Application Development
    • ASP.NET Core Hosting support through Hosting Bundle
    • Management Tools
    • IIS Management Console
  7. Install URL Rewrite.
  8. Install Application Request Routing.
  9. Open IIS Manager and enable proxy:
    • Select server node.
    • Open Application Request Routing Cache.
    • Click Server Proxy Settings.
    • Enable Enable proxy.
    • Apply changes.

Verification checkpoint:

CheckExpected Result
IIS Manager opensStart menu search for IIS Manager opens the management console.
URL Rewrite is installedURL Rewrite appears in the IIS feature list.
ARR proxy is enabledServer node -> Application Request Routing Cache -> Server Proxy Settings has Enable proxy checked.
IIS responds locallyBrowsing http://localhost shows the IIS default page or configured site.

1.7.2 Install .NET 9 Hosting Bundle

  1. Download the .NET 9 Hosting Bundle from Microsoft.
  2. Run the installer as Administrator.
  3. Restart IIS:
iisreset
  1. Confirm runtime installation:
dotnet --list-runtimes
dotnet --list-sdks

Verification checkpoint:

CheckCommand / Expected Result
ASP.NET Core runtime installeddotnet --list-runtimes includes Microsoft.AspNetCore.App 9.0.
.NET runtime installeddotnet --list-runtimes includes Microsoft.NETCore.App 9.0.
IIS module registeredApps published with ASP.NET Core web.config can start behind IIS after iisreset.

1.7.3 Install Node and Yarn for Web Build

Install Node.js 20 LTS, then install Yarn:

npm install --global yarn
node --version
yarn --version

Build the web application:

cd C:\Source\TeraSupport\Web
yarn install --frozen-lockfile
yarn build

The production web files are generated in:

Web\dist

1.7.4 Prepare PostgreSQL on Windows

Option A: Use a dedicated PostgreSQL server.

Option B: Install PostgreSQL directly on the Windows Server.

Create the required databases:

CREATE DATABASE "TSUserDB";
CREATE DATABASE "TSCallCenterDb";
CREATE DATABASE "TSCRMDb";
CREATE DATABASE "TSDeviceDB";
CREATE DATABASE "TSMailDB";
CREATE DATABASE "TSTicketDb";

Create a production database user:

CREATE USER terasupport_prod WITH PASSWORD '<strong-password>';
GRANT ALL PRIVILEGES ON DATABASE "TSUserDB" TO terasupport_prod;
GRANT ALL PRIVILEGES ON DATABASE "TSCallCenterDb" TO terasupport_prod;
GRANT ALL PRIVILEGES ON DATABASE "TSCRMDb" TO terasupport_prod;
GRANT ALL PRIVILEGES ON DATABASE "TSDeviceDB" TO terasupport_prod;
GRANT ALL PRIVILEGES ON DATABASE "TSMailDB" TO terasupport_prod;
GRANT ALL PRIVILEGES ON DATABASE "TSTicketDb" TO terasupport_prod;

Verification checkpoint:

CheckCommand / Expected Result
PostgreSQL service is runningWindows Services shows PostgreSQL running, or pg_isready -h localhost -p 5432 returns accepting connections.
Databases existpgAdmin or psql -l shows TSUserDB, TSTicketDb, TSCRMDb, TSCallCenterDb, TSDeviceDB, and TSMailDB.
Production user has accesspsql -h localhost -U terasupport_prod -d TSTicketDb connects successfully.

1.7.5 Prepare Kafka on Windows

Recommended production option: run Kafka on a Linux/Docker infrastructure and allow Windows-hosted .NET services to connect through KAFKA_HOST and KAFKA_PORT.

If Kafka must run on Windows:

  1. Install Java Runtime required by the Kafka distribution.
  2. Download Apache Kafka or Confluent Platform.
  3. Extract Kafka to:
C:\kafka
  1. Configure Zookeeper and Kafka listeners.
  2. Start Zookeeper:
cd C:\kafka
.\bin\windows\zookeeper-server-start.bat .\config\zookeeper.properties
  1. Start Kafka:
cd C:\kafka
.\bin\windows\kafka-server-start.bat .\config\server.properties
  1. Create topics manually if auto-create is disabled:
.\bin\windows\kafka-topics.bat --bootstrap-server localhost:9092 --create --topic ticket-create-topic
.\bin\windows\kafka-topics.bat --bootstrap-server localhost:9092 --create --topic mail-ticket-topic
.\bin\windows\kafka-topics.bat --bootstrap-server localhost:9092 --create --topic mail-message-topic
.\bin\windows\kafka-topics.bat --bootstrap-server localhost:9092 --create --topic email-send-topic
.\bin\windows\kafka-topics.bat --bootstrap-server localhost:9092 --create --topic message-create-topic
.\bin\windows\kafka-topics.bat --bootstrap-server localhost:9092 --create --topic create-user-request-topic
.\bin\windows\kafka-topics.bat --bootstrap-server localhost:9092 --create --topic user-request-update-topic
  1. List topics:
.\bin\windows\kafka-topics.bat --bootstrap-server localhost:9092 --list

Set service variables:

KAFKA_HOST=localhost
KAFKA_PORT=9092

Verification checkpoint:

CheckCommand / Expected Result
Kafka process is runningKafka console stays open without fatal startup errors.
Topics can be listedkafka-topics.bat --bootstrap-server localhost:9092 --list returns topic names.
TeraSupport topics existRequired topics such as ticket-create-topic, mail-ticket-topic, and email-send-topic are listed.

1.7.6 Publish .NET Services

From the solution root:

cd C:\Source\TeraSupport
dotnet restore TS.sln
dotnet build TS.sln -c Release

Publish each service:

dotnet publish ApiGateway\ApiGateway.csproj -c Release -o C:\inetpub\terasupport\apigateway
dotnet publish User\User.csproj -c Release -o C:\inetpub\terasupport\user
dotnet publish Ticketing\Ticketing.csproj -c Release -o C:\inetpub\terasupport\ticketing
dotnet publish CRM\CRM.csproj -c Release -o C:\inetpub\terasupport\crm
dotnet publish CallCenter\CallCenter.csproj -c Release -o C:\inetpub\terasupport\callcenter
dotnet publish DeviceManagement\DeviceManagement.csproj -c Release -o C:\inetpub\terasupport\devicemanagement
dotnet publish Mail\Mail.csproj -c Release -o C:\inetpub\terasupport\mail
dotnet publish Meta\Meta.csproj -c Release -o C:\inetpub\terasupport\meta

Copy web build output:

New-Item -ItemType Directory -Force C:\inetpub\terasupport\web
Copy-Item C:\Source\TeraSupport\Web\dist\* C:\inetpub\terasupport\web -Recurse -Force

1.7.7 IIS Application Pools

Create one app pool per .NET service and one for the web app.

App Pool.NET CLR VersionPipelineIdentity
TeraSupport.ApiGatewayNo Managed CodeIntegratedApplicationPoolIdentity
TeraSupport.UserNo Managed CodeIntegratedApplicationPoolIdentity
TeraSupport.TicketingNo Managed CodeIntegratedApplicationPoolIdentity
TeraSupport.CRMNo Managed CodeIntegratedApplicationPoolIdentity
TeraSupport.CallCenterNo Managed CodeIntegratedApplicationPoolIdentity
TeraSupport.DeviceManagementNo Managed CodeIntegratedApplicationPoolIdentity
TeraSupport.MailNo Managed CodeIntegratedApplicationPoolIdentity
TeraSupport.MetaNo Managed CodeIntegratedApplicationPoolIdentity
TeraSupport.WebNo Managed CodeIntegratedApplicationPoolIdentity

Recommended app pool settings:

SettingValue
Start ModeAlwaysRunning
Idle Time-out0
Rapid-Fail ProtectionEnabled
RecyclingSchedule during maintenance window
Load User ProfileTrue

Verification checkpoint:

CheckExpected Result
App pools existOne app pool exists for each TeraSupport service and web app.
Runtime mode is correctEach pool uses .NET CLR Version: No Managed Code.
Startup behavior is correctStart Mode is AlwaysRunning and Idle Time-out is 0.

1.7.8 IIS Site and Binding Plan

Recommended Windows deployment pattern:

IIS SiteBindingPhysical Path
TeraSupport.Webhttps://support.example.orgC:\inetpub\terasupport\web
TeraSupport.ApiGatewayhttp://localhost:5256 or private portC:\inetpub\terasupport\apigateway
TeraSupport.Userhttp://localhost:5184 or private portC:\inetpub\terasupport\user
TeraSupport.Ticketinghttp://localhost:5193 or private portC:\inetpub\terasupport\ticketing
TeraSupport.CRMhttp://localhost:5265 or private portC:\inetpub\terasupport\crm
TeraSupport.CallCenterhttp://localhost:5042 or private portC:\inetpub\terasupport\callcenter
TeraSupport.DeviceManagementhttp://localhost:5028 or private portC:\inetpub\terasupport\devicemanagement
TeraSupport.Mailhttp://localhost:5070 or private portC:\inetpub\terasupport\mail
TeraSupport.Metahttp://localhost:5121 or private portC:\inetpub\terasupport\meta

Public traffic should enter through the web site and /ts-api should proxy to the API Gateway.

1.7.9 IIS Environment Variables

For each IIS application:

  1. Open IIS Manager.
  2. Select the application.
  3. Open Configuration Editor.
  4. Select system.webServer/aspNetCore.
  5. Add environment variables under environmentVariables.
  6. Apply changes.
  7. Recycle the app pool.

Example for Ticketing:

<environmentVariables>
<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Production" />
<environmentVariable name="CONNECTIONSTRINGS__DBLOCATION" value="Host=<db-host>;Port=5432;Database=TSTicketDb;Username=<user>;Password=<password>" />
<environmentVariable name="FILESETTING__FILEBASEURL" value="https://support.example.org/files/" />
<environmentVariable name="USERSERVICE__BASEURL" value="https://support.example.org/ts-api/" />
<environmentVariable name="APISETTINGS__BASEURL" value="https://support.example.org/ts-api/" />
<environmentVariable name="KAFKA_HOST" value="<kafka-host>" />
<environmentVariable name="KAFKA_PORT" value="9092" />
<environmentVariable name="JWT__KEY" value="<production-jwt-secret>" />
</environmentVariables>

Verification checkpoint:

CheckExpected Result
API Gateway variables are setOcelot downstream hosts/ports point to the correct service bindings.
Service database variables are setEach service points to its own PostgreSQL database.
Security variables are setJWT, SMTP, DB, and integration secrets use production values, not sample values.
Changes are activeApp pools are recycled after updating variables.

1.7.10 IIS URL Rewrite Rules

Add a web.config to the web site root:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="Proxy API Gateway" stopProcessing="true">
<match url="^ts-api/(.*)" />
<action type="Rewrite" url="http://localhost:5256/ts-api/{R:1}" />
</rule>
<rule name="Proxy Files" stopProcessing="true">
<match url="^files/(.*)" />
<action type="Rewrite" url="http://localhost:5256/files/{R:1}" />
</rule>
<rule name="Proxy Swagger" stopProcessing="true">
<match url="^swagger/(.*)" />
<action type="Rewrite" url="http://localhost:5256/swagger/{R:1}" />
</rule>
<rule name="React SPA Fallback" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="/index.html" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>

If API Gateway listens on HTTPS instead of HTTP, update the rewrite target accordingly.

1.7.11 Windows Verification

Verify in this order:

  1. PostgreSQL is running.
  2. Kafka and Zookeeper are running.
  3. User service starts.
  4. Ticketing service starts.
  5. Mail service starts and subscribes to Kafka.
  6. CRM service starts and subscribes to Kafka.
  7. API Gateway starts and loads Ocelot routes.
  8. Web app opens from IIS.
  9. /ts-api/swagger or gateway Swagger opens.
  10. Login works.
  11. Ticket creation works.
  12. Ticket assignment triggers Kafka/mail logs.
  13. Attachment upload/download works.
  14. Reports open and export.

Verification checkpoint:

CheckExpected Result
Public web site loadshttps://<host> opens the TeraSupport login page.
API Gateway loadshttps://<host>/ts-api/swagger or the configured gateway Swagger URL opens.
Authentication worksA valid user can sign in and load the dashboard.
Ticket workflow worksA ticket can be created, assigned, updated, and resolved.
Logs are cleanIIS logs and Windows Event Viewer do not show repeated startup failures.

1.8 Linux Deployment With Docker and Portainer

1.8.0 Linux / Docker / Portainer Deployment Flow

100%
50%300%
Linux Docker Portainer deployment flow diagram

Editable source: LinuxDeploymentFlow.drawio

1.8.0.1 Docker Image Build Map

100%
50%300%
Docker image build map diagram

Editable source: Docker Image Build.drawio

1.8.1 Linux Server Preparation

  1. Install Ubuntu Server.
  2. Update packages:
sudo apt update
sudo apt upgrade -y
  1. Install Docker:
sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
  1. Enable Docker:
sudo systemctl enable docker
sudo systemctl start docker
docker --version
docker compose version
  1. Create the Docker network used by the compose files:
docker network create ts-network

Verification checkpoint:

CheckCommand / Expected Result
Docker service is runningsudo systemctl status docker shows active/running.
Docker workssudo docker run hello-world completes successfully.
Compose plugin worksdocker compose version prints a version.
Shared network existsdocker network ls includes ts-network.

1.8.2 Install Portainer

docker volume create portainer_data
docker run -d \
-p 9443:9443 \
-p 9000:9000 \
--name portainer \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
portainer/portainer-ce:latest

Open:

https://<server-ip>:9443

Create the initial Portainer admin account and connect to the local Docker environment.

Verification checkpoint:

CheckExpected Result
Portainer openshttps://<server-ip>:9443 loads the Portainer setup page.
Admin user existsThe first admin user has been created and password stored securely.
Local environment is connectedPortainer can list local Docker containers, images, networks, and volumes.

1.8.3 Build Docker Images

From the solution root:

cd /opt/TeraSupport
docker build -t arcapps/terasupport-api-gateway:latest -f ApiGateway/Dockerfile .
docker build -t arcapps/terasupport-user-service:latest -f User/Dockerfile .
docker build -t arcapps/terasupport-ticketing-service:latest -f Ticketing/Dockerfile .
docker build -t arcapps/terasupport-crm-service:latest -f CRM/Dockerfile .
docker build -t arcapps/terasupport-callcenter-service:latest -f CallCenter/Dockerfile .
docker build -t arcapps/terasupport-devicemanagement-service:latest -f DeviceManagement/Dockerfile .
docker build -t arcapps/terasupport-mail-service:latest -f Mail/Dockerfile .
docker build -t arcapps/terasupport-meta-service:latest -f Meta/Dockerfile .
docker build -t arcapps/terasupport-web-service:latest -f Web/Dockerfile --build-arg VITE_API_URL=/ts-api .
docker build -t arcapps/terasupport-nginx-service:latest -f Nginx/Dockerfile .

If using a private registry:

docker login <registry-host>
docker tag arcapps/terasupport-api-gateway:latest <registry-host>/terasupport-api-gateway:latest
docker push <registry-host>/terasupport-api-gateway:latest

Repeat tagging and pushing for all images.

Verification checkpoint:

CheckCommand / Expected Result
Images exist locally`docker images
Tags match compose filesImage names and tags match prod.compose.yml.
Registry push is completeIf using a registry, docker pull <registry-host>/<image>:<tag> succeeds from the deployment server.

1.8.4 Deploy PostgreSQL Stack in Portainer

Use:

prod.postgres.compose.yml

Steps:

  1. Open Portainer.
  2. Go to Stacks.
  3. Click Add stack.
  4. Name it terasupport-db.
  5. Paste the content of prod.postgres.compose.yml.
  6. Replace all sample passwords with production secrets.
  7. Deploy the stack.
  8. Confirm all PostgreSQL containers are healthy.

Current compose creates separate PostgreSQL containers:

ContainerHost PortDatabase
postgres_user5432TSUserDB
postgres_callcenter5433TSCallCenterDb
postgres_crm5434TSCRMDb
postgres_device5435TSDeviceDB
postgres_mail5436TSMailDB
postgres_ticket5437TSTicketDb

Public-safe Portainer compose template:

version: "3.8"

services:
postgres_user:
image: postgres:17.5
restart: always
ports:
- "5432:5432"
environment:
POSTGRES_USER: <POSTGRES_USER>
POSTGRES_PASSWORD: <POSTGRES_PASSWORD>
POSTGRES_DB: TSUserDB
volumes:
- local_pgdata_user:/var/lib/postgresql/data
networks:
- ts-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U <POSTGRES_USER>"]
interval: 5s
timeout: 5s
retries: 5

postgres_callcenter:
image: postgres:17.5
restart: always
ports:
- "5433:5432"
environment:
POSTGRES_USER: <POSTGRES_USER>
POSTGRES_PASSWORD: <POSTGRES_PASSWORD>
POSTGRES_DB: TSCallCenterDb
volumes:
- local_pgdata_callcenter:/var/lib/postgresql/data
networks:
- ts-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U <POSTGRES_USER>"]
interval: 5s
timeout: 5s
retries: 5

postgres_crm:
image: postgres:17.5
restart: always
ports:
- "5434:5432"
environment:
POSTGRES_USER: <POSTGRES_USER>
POSTGRES_PASSWORD: <POSTGRES_PASSWORD>
POSTGRES_DB: TSCRMDb
volumes:
- local_pgdata_crm:/var/lib/postgresql/data
networks:
- ts-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U <POSTGRES_USER>"]
interval: 5s
timeout: 5s
retries: 5

postgres_device:
image: postgres:17.5
restart: always
ports:
- "5435:5432"
environment:
POSTGRES_USER: <POSTGRES_USER>
POSTGRES_PASSWORD: <POSTGRES_PASSWORD>
POSTGRES_DB: TSDeviceDB
volumes:
- local_pgdata_device:/var/lib/postgresql/data
networks:
- ts-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U <POSTGRES_USER>"]
interval: 5s
timeout: 5s
retries: 5

postgres_mail:
image: postgres:17.5
restart: always
ports:
- "5436:5432"
environment:
POSTGRES_USER: <POSTGRES_USER>
POSTGRES_PASSWORD: <POSTGRES_PASSWORD>
POSTGRES_DB: TSMailDB
volumes:
- local_pgdata_mail:/var/lib/postgresql/data
networks:
- ts-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U <POSTGRES_USER>"]
interval: 5s
timeout: 5s
retries: 5

postgres_ticket:
image: postgres:17.5
restart: always
ports:
- "5437:5432"
environment:
POSTGRES_USER: <POSTGRES_USER>
POSTGRES_PASSWORD: <POSTGRES_PASSWORD>
POSTGRES_DB: TSTicketDb
volumes:
- local_pgdata_ticket:/var/lib/postgresql/data
networks:
- ts-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U <POSTGRES_USER>"]
interval: 5s
timeout: 5s
retries: 5

volumes:
local_pgdata_user:
local_pgdata_callcenter:
local_pgdata_crm:
local_pgdata_device:
local_pgdata_mail:
local_pgdata_ticket:

networks:
ts-network:
external: true

Do not paste real database passwords into the public manual. Replace <POSTGRES_USER> and <POSTGRES_PASSWORD> only inside Portainer or a client-controlled secret vault.

If the production Docker network is named tera_networks instead of ts-network, replace the network name consistently in all three Portainer stacks. Do not mix both names.

Verification checkpoint:

CheckExpected Result
Stack is runningPortainer shows the terasupport-db stack as deployed/running.
PostgreSQL containers are healthyUser, Ticket, CRM, CallCenter, Device, and Mail database containers are running.
Volumes are persistentEach database container has a named volume mapped for data persistence.

1.8.5 Deploy Kafka Stack in Portainer

Use:

prod.kafka.compose.yml

Steps:

  1. Open Stacks in Portainer.
  2. Click Add stack.
  3. Name it terasupport-kafka.
  4. Paste the Kafka compose content.
  5. Confirm ts-network already exists.
  6. Deploy the stack.
  7. Open Kafdrop on:
http://<server-ip>:9001
  1. Confirm Kafka broker is visible.
  2. Confirm topics are created after services publish events.

For Docker services, set:

KAFKA_HOST=kafka
KAFKA_PORT=9092

For host-based tools connecting from the Linux server, use:

KAFKA_HOST=localhost
KAFKA_PORT=29092

Public-safe Portainer compose template:

version: "3.8"

networks:
ts-network:
external: true

volumes:
zookeeper_data:
zookeeper_log:
kafka_data:

services:
zookeeper:
image: confluentinc/cp-zookeeper:7.4.0
container_name: zookeeper
networks:
- ts-network
ports:
- "2181:2181"
volumes:
- zookeeper_data:/var/lib/zookeeper/data
- zookeeper_log:/var/lib/zookeeper/log
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
ZOOKEEPER_DATA_DIR: /var/lib/zookeeper/data
ZOOKEEPER_DATA_LOG_DIR: /var/lib/zookeeper/log

kafka:
image: confluentinc/cp-kafka:7.4.0
container_name: kafka
hostname: kafka
networks:
- ts-network
depends_on:
- zookeeper
ports:
- "9092:9092"
- "29092:29092"
volumes:
- kafka_data:/var/lib/kafka/data
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,PLAINTEXT_HOST://0.0.0.0:29092
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://<SERVER_IP_OR_DNS>:29092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
KAFKA_LOG_DIRS: /var/lib/kafka/data

kafka-ui:
image: provectuslabs/kafka-ui:latest
container_name: kafka-ui
networks:
- ts-network
depends_on:
- kafka
ports:
- "9001:8080"
environment:
KAFKA_CLUSTERS_0_NAME: terasupport
KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092
KAFKA_CLUSTERS_0_ZOOKEEPER: zookeeper:2181

Replace <SERVER_IP_OR_DNS> with the Linux server IP or internal DNS name used by host-based tools. Do not publish private IP addresses in public documentation.

1.8.6 Deploy Application Stack in Portainer

Use:

prod.compose.yml

Steps:

  1. Open Stacks.
  2. Click Add stack.
  3. Name it terasupport-app.
  4. Paste the content of prod.compose.yml.
  5. Replace sample secrets:
    • Database passwords
    • JWT__KEY
    • Remote device token
    • Mail credentials
    • Meta/Facebook/Instagram credentials
    • Cloudflare tunnel token, if used
  6. Confirm all image names point to your registry.
  7. Deploy the stack.
  8. Open Containers and confirm all app containers are running.

Main services in production compose:

ServiceImage
apigatewayarcapps/terasupport-api-gateway:latest
userarcapps/terasupport-user-service:latest
ticketingarcapps/terasupport-ticketing-service:latest
crmarcapps/terasupport-crm-service:latest
callcenterarcapps/terasupport-callcenter-service:latest
devicemanagementarcapps/terasupport-devicemanagement-service:latest
mailarcapps/terasupport-mail-service:latest
webarcapps/terasupport-web-service:latest
nginxarcapps/terasupport-nginx-service:latest

Public-safe Portainer compose template:

version: "3.8"

services:
apigateway:
image: <REGISTRY>/terasupport-api-gateway:<TAG>
networks:
- ts-network
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ALLOWEDHOSTS=<ALLOWED_HOSTS>
- JWT__KEY=<JWT_SIGNING_KEY>
- OCELOTVARIABLES__DOWNSTREAMSCHEME=http
- OCELOTVARIABLES__USERMANAGEMENTSERVICEHOST=user
- OCELOTVARIABLES__USERMANAGEMENTSERVICEPORT=8080
- OCELOTVARIABLES__TICKETINGSYSTEMSERVICEHOST=ticketing
- OCELOTVARIABLES__TICKETINGSYSTEMSERVICEPORT=8080
- OCELOTVARIABLES__CRMSERVICEHOST=crm
- OCELOTVARIABLES__CRMSERVICEPORT=8080
- OCELOTVARIABLES__CALLCENTERHOST=callcenter
- OCELOTVARIABLES__CALLCENTERPORT=8080
- OCELOTVARIABLES__DEVICEHOST=devicemanagement
- OCELOTVARIABLES__DEVICEPORT=8080
- OCELOTVARIABLES__MAILSERVICEHOST=mail
- OCELOTVARIABLES__MAILSERVICEPORT=8080

user:
image: <REGISTRY>/terasupport-user-service:<TAG>
networks:
- ts-network
volumes:
- user-data:/app/wwwroot
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ALLOWEDHOSTS=<ALLOWED_HOSTS>
- CONNECTIONSTRINGS__DBLOCATION=Host=postgres_user;Port=5432;Database=TSUserDB;Username=<POSTGRES_USER>;Password=<POSTGRES_PASSWORD>

ticketing:
image: <REGISTRY>/terasupport-ticketing-service:<TAG>
networks:
- ts-network
volumes:
- ticketing-data:/app/wwwroot
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ALLOWEDHOSTS=<ALLOWED_HOSTS>
- CONNECTIONSTRINGS__DBLOCATION=Host=postgres_ticket;Port=5432;Database=TSTicketDb;Username=<POSTGRES_USER>;Password=<POSTGRES_PASSWORD>
- FILESETTING__FILEBASEURL=<PUBLIC_FILE_BASE_URL>
- USERSERVICE__BASEURL=http://user:8080/
- APISETTINGS__BASEURL=http://user:8080/
- JWT__KEY=<JWT_SIGNING_KEY>
- KAFKA_HOST=kafka
- KAFKA_PORT=9092

crm:
image: <REGISTRY>/terasupport-crm-service:<TAG>
networks:
- ts-network
volumes:
- crm-data:/app/wwwroot
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ALLOWEDHOSTS=<ALLOWED_HOSTS>
- CONNECTIONSTRINGS__DBLOCATION=Host=postgres_crm;Port=5432;Database=TSCRMDb;Username=<POSTGRES_USER>;Password=<POSTGRES_PASSWORD>
- BASEAPISETTINGS__USERSERVICE=http://user:8080/user-api/
- FILESETTING__FILEBASEURL=<PUBLIC_FILE_BASE_URL>
- KAFKA_HOST=kafka
- KAFKA_PORT=9092

callcenter:
image: <REGISTRY>/terasupport-callcenter-service:<TAG>
networks:
- ts-network
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ALLOWEDHOSTS=<ALLOWED_HOSTS>
- CONNECTIONSTRINGS__DBLOCATION=Host=postgres_callcenter;Port=5432;Database=TSCallCenterDb;Username=<POSTGRES_USER>;Password=<POSTGRES_PASSWORD>
- APISETTINGS__BASEURL=http://user:8080/

devicemanagement:
image: <REGISTRY>/terasupport-devicemanagement-service:<TAG>
networks:
- ts-network
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ALLOWEDHOSTS=<ALLOWED_HOSTS>
- CONNECTIONSTRINGS__DBLOCATION=Host=postgres_device;Port=5432;Database=TSDeviceDB;Username=<POSTGRES_USER>;Password=<POSTGRES_PASSWORD>
- APISETTINGS__BASEURL=http://user:8080/
- APISETTINGS__TICKETAPIURL=http://ticketing:8080/

mail:
image: <REGISTRY>/terasupport-mail-service:<TAG>
networks:
- ts-network
volumes:
- mail-data:/app/wwwroot
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ALLOWEDHOSTS=<ALLOWED_HOSTS>
- DB_HOST=postgres_mail
- DB_PORT=5432
- DB_NAME=TSMailDB
- DB_USER=<POSTGRES_USER>
- DB_PASSWORD=<POSTGRES_PASSWORD>
- KAFKA_HOST=kafka
- KAFKA_PORT=9092
- TicketHTTP_HOST=<PUBLIC_TICKET_VIEW_URL>

web:
image: <REGISTRY>/terasupport-web-service:<TAG>
networks:
- ts-network
environment:
- VITE_API_URL=/ts-api

nginx:
image: <REGISTRY>/terasupport-nginx-service:<TAG>
networks:
- ts-network
ports:
- "<PUBLIC_HTTP_PORT>:80"
depends_on:
- apigateway
- web
- callcenter
- crm
- devicemanagement
- mail
- ticketing
- user

networks:
ts-network:
external: true

volumes:
user-data:
mail-data:
crm-data:
ticketing-data:

The public template intentionally removes optional parts. Do not include these in the public compose example unless the client explicitly enables them and provides values through a private channel:

Optional PartPublic Manual Guidance
Cloudflare TunnelRemove from the public template. Add only in private deployment notes with <CLOUDFLARE_TUNNEL_TOKEN>.
Meta/Facebook/Instagram serviceRemove unless the integration is part of the production scope. Use placeholders only.
Remote device integration tokenRemove unless the module is enabled. Use <REMOTE_DEVICE_AUTH_TOKEN> only in private deployment values.
Ollama or AI endpointRemove unless the AI service is deployed and approved for production.
Real public domain, IP, JWT key, DB password, SMTP credentialNever publish. Use placeholders in documentation and store real values in the client vault.

If Meta service is required in production, add it privately with placeholders only:

meta:
image: <REGISTRY>/terasupport-meta-service:<TAG>
networks:
- ts-network
environment:
- ASPNETCORE_ENVIRONMENT=Production
- CONNECTIONSTRINGS__DBLOCATION=Host=<META_DB_HOST>;Port=5432;Database=TSMetaDb;Username=<POSTGRES_USER>;Password=<POSTGRES_PASSWORD>
- BASEAPISETTINGS__USERSERVICE=http://user:8080/user-api/

Verification checkpoint:

CheckExpected Result
Application stack is runningPortainer shows the application stack as deployed/running.
Containers are stableAPI Gateway, services, web, and Nginx containers do not restart repeatedly.
Service logs are cleandocker logs <container> does not show repeated database, Kafka, JWT, or routing errors.
Environment values are production-readySample secrets and placeholder URLs are replaced before go-live.

1.8.7 Nginx Load Balancer Configuration

The repository includes:

Nginx/default.conf

Current routing pattern:

PathUpstream
/web:80
/ts-api/apigateway:8080
/files/apigateway:8080
/swagger/apigateway:8080
/ollama-api/External Ollama host

Production Nginx should include proxy headers:

location /ts-api/ {
proxy_pass http://apigateway;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300s;
}

If multiple API Gateway replicas are used, update the upstream:

upstream apigateway {
least_conn;
server apigateway-1:8080;
server apigateway-2:8080;
}

With Docker Compose scaling, run:

docker compose -f prod.compose.yml up -d --scale apigateway=2

Only scale stateless services. Do not scale PostgreSQL, Kafka, or stateful services without a proper clustering design.

1.8.8 Public HTTPS / Load Balancer Options

Choose one production entry option:

OptionDescription
Host NginxInstall Nginx on Linux host and proxy to container port 3698.
Container NginxUse included nginx container and publish 80/443.
Cloudflare TunnelOptional. Remove from the public compose template. Add only in private deployment notes using <CLOUDFLARE_TUNNEL_TOKEN>.
External Load BalancerPoint external LB to Linux host and published Nginx port.

For host Nginx:

server {
listen 80;
server_name support.example.org;
return 301 https://$host$request_uri;
}

server {
listen 443 ssl http2;
server_name support.example.org;

ssl_certificate /etc/letsencrypt/live/support.example.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/support.example.org/privkey.pem;

client_max_body_size 30M;

location / {
proxy_pass http://127.0.0.1:3698;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

Verification checkpoint:

CheckExpected Result
Nginx config is validnginx -t succeeds if using host Nginx; container logs are clean if using container Nginx.
Public URL loadshttps://<domain> opens the web login page.
API routes workhttps://<domain>/ts-api/swagger reaches API Gateway.
TLS is correctBrowser certificate matches the public domain and is not expired.

1.8.9 Linux Verification

Run:

docker ps
docker logs kafka --tail=100
docker logs apigateway --tail=100
docker logs ticketing --tail=100
docker logs mail --tail=100
curl -I http://localhost:3698
curl -I http://localhost:3698/swagger/

Verify in browser:

URLExpected Result
http://<server-ip>:3698Web app loads.
http://<server-ip>:3698/swagger/Gateway Swagger loads.
http://<server-ip>:9001Kafdrop loads.
https://support.example.orgProduction HTTPS site loads.

Functional verification:

  1. Login as administrator.
  2. Open Admin Panel.
  3. Create or verify organization.
  4. Create user.
  5. Create region and facility.
  6. Create category and team.
  7. Create ticket from portal.
  8. Assign ticket.
  9. Send reply.
  10. Confirm Kafka topics receive events.
  11. Confirm email notification is sent.
  12. Confirm reports load.

1.9 CQRS / MediatR Service Configuration Notes

The backend services use CQRS-style modules and MediatR handlers. Deployment must preserve:

RequirementWhy It Matters
Service-specific database connectionEach service owns its data context.
Correct CONNECTIONSTRINGS__DBLOCATIONEF Core UseNpgsql reads this value.
Correct Kafka host/portProducers and consumers read KAFKA_HOST and KAFKA_PORT.
Correct downstream service URLsHandlers call other services for user, ticket, CRM, device, and mail workflows.
JWT key consistencyGateway and protected services must validate the same token signing key.
File base URL consistencyTicket/CRM/Mail attachments must resolve through the correct public URL.

Because handlers are invoked by MediatR at runtime, a service may start successfully but fail business workflows if one of these values points to the wrong service or environment.


1.10 Backup and Restore

1.10.1 PostgreSQL Backup

Run daily backups for all service databases:

pg_dump -h <db-host> -U <db-user> -d TSUserDB > TSUserDB.sql
pg_dump -h <db-host> -U <db-user> -d TSTicketDb > TSTicketDb.sql
pg_dump -h <db-host> -U <db-user> -d TSCRMDb > TSCRMDb.sql
pg_dump -h <db-host> -U <db-user> -d TSCallCenterDb > TSCallCenterDb.sql
pg_dump -h <db-host> -U <db-user> -d TSDeviceDB > TSDeviceDB.sql
pg_dump -h <db-host> -U <db-user> -d TSMailDB > TSMailDB.sql

1.10.2 Docker Volume Backup

Back up volumes used by:

  • PostgreSQL
  • ticketing-data
  • crm-data
  • mail-data
  • user-data
  • ollama_data, if Ollama is used locally

1.11 Final Go-Live Checklist

AreaCheck
Windows ServerIIS role, Hosting Bundle, URL Rewrite, ARR, app pools, bindings configured.
Linux ServerDocker, Portainer, network, stacks, Nginx/load balancer configured.
DatabaseAll service databases created and backed up.
KafkaBroker running, topics created or auto-created, Kafdrop verified.
API GatewayOcelot routes point to correct downstream services.
ServicesAll .NET services start and expose Swagger/OpenAPI where enabled.
WebVite build uses correct VITE_API_URL.
SecurityHTTPS enabled; secrets replaced; sample credentials removed.
File Uploadsclient_max_body_size and attachment storage verified.
ReportsTicketing report endpoints and UI reports verified.
NotificationsMail service, Kafka events, and SMTP delivery verified.
DocumentationDownload links, environment values, and verification results are recorded.

Use these links during deployment. Prefer official vendor pages so installers receive current security updates.

AreaLinkPurpose
.NET 9.NET 9 downloadsDownload SDK and ASP.NET Core Hosting Bundle.
ASP.NET Core on IISMicrosoft IIS hosting guideConfirm IIS role services and Hosting Bundle behavior.
IIS URL RewriteIIS URL RewriteInstall rewrite rules support for proxy and SPA fallback.
IIS ARRApplication Request RoutingEnable IIS reverse proxy support.
Node.jsNode.js downloadsInstall Node LTS for building the React/Vite web app.
YarnYarn installationInstall Yarn for web dependencies.
PostgreSQL WindowsPostgreSQL Windows installerInstall PostgreSQL server and optional pgAdmin.
pgAdminpgAdmin Windows downloadManage PostgreSQL databases visually.
KafkaApache Kafka QuickstartDownload Kafka and review broker/topic commands.
Docker EngineDocker Engine on UbuntuInstall Docker Engine and Compose plugin.
Portainer CEPortainer CE installInstall Portainer for stack deployment.
NginxNginx load balancingConfigure upstreams and reverse proxy routing.

For diagrams that should remain editable, keep the .drawio source files under docs/images/installation/diagrams/ and publish an exported SVG beside each source file for the documentation page.


1.13 Final Operator Notes

Before handover, record the following operational values in the client-controlled password vault or deployment register. Do not store secrets directly in this documentation.

ItemWhat to Record
Public URLProduction domain, DNS owner, TLS certificate owner, expiry date.
Windows/IIS pathIIS site names, app pool names, physical paths, private service ports.
Linux/Portainer pathServer IP, Portainer URL, stack names, registry host, image tags.
Database accessHostnames, database names, admin owner, backup schedule, restore contact.
Kafka accessBroker host/port, Kafdrop URL if enabled, topic management owner.
SecretsJWT key, DB passwords, SMTP secrets, integration tokens, certificate private key location.
Verification resultDate/time of successful login, ticket creation, assignment, attachment upload, mail event, and report export.