Overview of Server Installation Process (Prima v3.x)

Prima can be installed on-site or in the cloud, but is installed and managed by each client (licensed server). This allows each customer to own and protect their data, meet their own security standards and help their own users as they see fit.

Prima consists of:

  • Prima application server (Windows Server 2022 or newer) that hosts the Windows service application(s)
  • Microsoft SQL database (version 2019 or newer)
  • Clients (mainly Windows desktop, some internal web)
  • Digital storage (documents, slide images, etc)
In general, it is best to have two or three system set-ups: one for production and a scaled-down version for user and upgrade testing (and optionally a thrid for validation in a GLP environment). There are two stages to the installation: the prep work that is included here and running the installer. The installer does a lot for us. Not only does it install the software, but it configures the system to use SSL certs, configures firewall settings (locally), LDAP/AD and more. If all the prep work is completed and there aren't any hidden (e.g. security) hiccups, the Prima installation should only take 20 minutes.

Setting Up Server Hardware

As a general stance on supported versions of software, Fortelinea stays very much on top of using all new performance and security measures in new versions of software. This means newer versions are always better, as far as we are concerned.

Hardware Requirements

Hardware requirements can vary quite a bit from lab to lab based on the usage of the system. We like to set both minimum and recommended requirements. We find that, if as much as possible is done using virtual machines, systems can later be tweaked based on how the lab ends up using the system.

Note that recommended requirements are based on a lab with about 12 stations/users.

Prima Application Server

Parameter Minimum Recommended
CPU 3.0GHz, 2 cores 3.4+GHz, 8 cores
Memory 8GB 32GB
Storage 32GB 64GB
Operating System Windows Server 2019* Windows Server 2022 (or newer)

* Windows Server 2019 does not fully support grpc services and will incur a significant performance hit

Prima Database Server

While we list specs for a database server, feel free to host the database on a shared server, given the overhead to support Prima is available. If you choose to allocate an SQL server VM to Prima, I would recommend using one for both the test and production databases (two databases on a single server). This will save on costs and be one less thing to manage.

Expect database growth in the neighborhood of 1GB per year

Parameter Minimum Recommended
CPU 3.0GHz, 2 cores 3.4+GHz, 4 cores
Memory 16GB 64GB
Storage 8GB (prefer autogrow) 16GB (prefer autogrow)
Operating System SQL Server 2019 SQL Server 2022

Shared Storage

Storage is highly configurable, but is separated here for ease of planning and use.

Parameter Minimum Recommended
Digital Documents 4GB 80GB
Digital Images 16GB 1TB

Installing Features and Drivers, Etc.

  • Assign a vanity name to the servers (e.g. prima.mycompany.com, prima-test.mycompany.com or whatever you want)

    Using a human-readable vanity name makes remembering the server address easier when installing clients, accessing your internal Prima website, etc.

  • Ensure that the .NET 10 Desktop Runtime and the ASP.NET Core runtime are installed on the application server
  • It's highly recommended to install Visual Studio Code for editing config files on the app server. It will save a ton of time, effort and frustration.

Service Users and Access

User Setup
  1. Fortelinea Team members will need access to the application server and database. This will save untold amounts of time in the long run. There are three methods, so choose one that best fits your scenario:
    1. Recommended Add a new account for the Fortelinea dev team (e.g. FortelineaPrimaAdmin). This account should have admin rights on the app server and full rights to the database. This account will be used for installations and updates.
    2. Setup individual accounts for at least two Fortelinea developers (will require more maintenance). These accounts will be used for installations and updates (any further mentions of FortelineaPrimaAdmin will apply to these individual accounts).
    3. Assign an IT contact that can allow access or perform the work over a screen share. This is fairly common, but not recommended; Fortelinea support cannot respond ot the lab as quickly and any work over the screen share is much slower
  2. Add a new service account for the application and database (e.g. prima). This account should have db_reader and db_writer access to the SQL database and the ability to read and write files from a network file share.
  3. Add a new admin account for the database (e.g. primaAdmin). This account is used by the installation/upgrade process to make table changes and other migrations. Separating this access to a different database user is an extra step in security, but you could choose to give this level of access to the service user instead.

SSL Certificates

Prima uses an SSL certificate to encrypt traffic sent to the clients, just like an https website. While many companies have a system that places certificates on all computers, some may need to generate a certificate manually. There are only a few requirements that must be met:

  • The certificate must match the name of the computer (this can be a vanity name, computer name or a wildcard)
      If using a vanity name:
    • The certificate must list the vanity name
    • The certificate should list the ip and/or any standard names (e.g. WK0123L21.mycompany.com) as secondary names
  • The Prima service user account must have access/privileges to the private key
  • The cert should be signed by your CA or an outside trusted CA; the cert needs to be trusted by the clients that will connect
  • The cert should be placed in LocalMachine\My of the certificate store
  • The cert should be at least 256 bit

Network Prep

Prima will communicate over your network to clients, hardware and other computer systems. It will also communicate with our web server. By ensuring all paths are clear, we can save a lot of troubleshooting time.

Ports to clients
80, 443 (standard http and https)
Prima Web service
https://prima.fortelinea.com
Ventana Connect
Port 55550 (IP on your network)
Arcos block and slide storage
(IP on your network)
Other
Discussed prior to installation

Also:

  • Ensure that any network proxy information has been set up. Prima will ask Windows for this information and will attempt to use it.
  • Make sure that a newer, secure browser on the application server can reach https://prima.fortelinea.com and that js is enabled. We will use this for downloading installers, updates, grabbing license info, troubleshooting, etc.

Record Settings for Prima Installation

Before we run the Prima installer, you'll want to make sure you have the following:

  • Prima does a great job of finding your Active Directory server, but it is best to be prepared. Make sure you know the address and port.
  • Know the connection string (or at least the address and relevant parameters) for the SQL server.

Extra Installation Info and Troubleshooting

SQL Backup

It is highly recommended to set up a database backup plan. If a backup/restore is something that you can allow Fortelinea or the prima database admin user to do, it can save time and effort later.

Troubleshooting

Prima Website Logs
As long as the application has access to talk to our server (https://prima.fortelinea.com), we get and log a variety of detailed information on crashes, exceptions, and configuration warnings. Usually, the website logs are the first tool Fortelinea developers use when troubleshooting.
Event Viewer
The Windows Event viewer is a great place to look. Most serious logs end up here. Be sure to check under Application and System event logs.
File logs
There are times when exceptions don't make it to the event viewer due to a crash and the logs don't make it to our website. The fallback are textual log files which can be found at C:\ProgramData\FortelineaSoftwareSystems\Prima\Logs

Server Installation Checklist

The following is meant to be a brief guideline, but the detailed sections should be followed for specifics:

  1. Requisition two Windows Server VMs for the Prima Application Server (test and production)
    1. Ensure a modern web browser (with js enable) is installed and can reach https://prima.fortelinea.com (set Windows proxy settings, if necessary)
    2. Ensure .Net Core Desktop runtime is installed
    3. Install a better editor than Notepad (e.g. Visual Studio Code)
    4. Create a Prima service user (network-capable) and assign logon rights
    5. Assign vanity names to the Prima servers
    6. Setup SSL certificates on the two application servers (ensure service user has PK rights)
    7. Download the 'packaged installer' from Fortelinea's website (https://prima.fortelinea.com/SoftwareRelease)
  2. Requisition an SQL Server (most cost-effective to have one SQL server with multiple Prima databases)
    1. Create two SQL databases: one for prod and one for test
    2. Create a Prima database admin user and assign db_owner rights to each of the Prima databases
    3. Ensure service user has read/write/logon for each database
    4. Ensure the SQL server has network access turned on and the appropriate ports are open

Docker Deployment

Pull prebuilt images from prima-registry.fortelinea.com and run the full Prima stack with Docker Compose — no build toolchain required on the production host.

Architecture

The stack runs five services:

Service Purpose Ports (host:container)
rabbitmq Message broker 5672, 15672 (management UI)
dbinit Run-to-completion job: EF Core migrations + optional fresh-DB seed none
worker Singleton background services + Hangfire server none
web-api REST, Razor pages, gRPC, OpenIddict 8080 (gRPC), 8081 (HTTP)
notification gRPC streaming endpoints for client connections 8082 → 8080 (gRPC), 8083 → 8081 (HTTP/1 health)

dbinit runs first; worker, web-api, and notification wait for it to exit successfully (service_completed_successfully) and for rabbitmq to be healthy.

Prerequisites

  • Docker Desktop (Windows/Mac) or Docker Engine (Linux)
  • A reachable SQL Server instance (this stack does not include a database container)

Production Deployment

docker/docker-compose.prod.yml is a standalone file that pulls prebuilt images from prima-registry.fortelinea.com instead of building from source. Use it alone — do not layer it on the base file:

# Pin a version (defaults to "latest" if unset)
export PRIMA_VERSION=3.2.1
docker compose -f docker/docker-compose.prod.yml up -d

It needs the same docker/.env runtime variables (DB_CONNECTION_STRING, RABBITMQ_PASSWORD, and any dbinit seed vars). NUGET_PAT is not required — nothing is built.

Environment Variables

Variable Description
DB_CONNECTION_STRING SQL Server connection string used by all Prima services
RABBITMQ_PASSWORD Password for the prima RabbitMQ broker user
DBINIT_COMMAND DbInit verb. Defaults to update (migrations only)
FortelineaWebConfig__ClientId (optional) Prima website login email — fresh-DB seed
FortelineaWebConfig__ClientSecret (optional) Prima website login password — fresh-DB seed
DBINIT_LAB_SITE_GUID (optional) Lab site GUID to configure — fresh-DB seed
DBINIT_ADMIN_EMAIL (optional) Initial admin email — fresh-DB seed
DBINIT_ADMIN_USERNAME (optional) Initial admin username — fresh-DB seed

The five optional seed variables are used together. When all are set and the database is fresh, dbinit runs update --seed and configures the lab site. When unset, dbinit runs migrations only.

Health Checks

worker, web-api, and notification each expose a health check probing /health/live. Check container health with:

docker inspect --format='{{.State.Health.Status}}' <container-name>

Troubleshooting

.env file causes errors when sourcing

If you see \r: command not found, the file has Windows line endings:

sed -i 's/\r$//' docker/.env

Port conflicts

If ports 8080/8081/8082/8083 or 5672/15672 are in use, edit the ports: mappings in docker/docker-compose.yml.

A service won't start

Confirm dbinit exited successfully and rabbitmq is healthy — the app services block on both:

docker compose -f docker/docker-compose.prod.yml ps
docker compose -f docker/docker-compose.prod.yml logs dbinit

OpenShift Deployment

Deploy Prima onto an OpenShift cluster (or any Kubernetes ≥ 1.25) using the provided manifests. Download the YAML files below, then work through the sections in order.

Download YAML Files

Architecture

All external traffic — REST, the Prima desktop (WPF) apps over gRPC, and notification streams over gRPC — uses one hostname. Three OpenShift Routes share that host and fan out by URL path.

                  Browsers, WPF apps, external REST clients
                                   |
                                   | HTTPS  (HTTP/1 or HTTP/2 via ALPN)
                                   v
                   +------------------------------------+
                   |  OpenShift router (HAProxy)        |
                   |  Single host: prima.example.com    |
                   |  Edge-TLS termination              |
                   +-+------------+-----------+---------+
                     |            |           |
              path:  | /grpc/     | /grpc/    | (catch-all)
                     | pubsub     |           |
                     |            |           |
                     v            v           v
              +-------------+ +-------+  +-------+
              | notification| |web-api| |web-api|
              |   :8080 h2c | |:8080  | |:8081  |
              | Route       | |h2c    | |HTTP/1 |
              | notification| |Route  | |Route  |
              |   -grpc     | |web-api| |web-api|
              +-------------+ |-grpc  | |-rest  |
                              +---+---+ +---+---+
                                  |         |
                                  v         v
                              +-----------------+
                              | Service web-api |  ClusterIP
                              | (grpc + http)   |
                              +--------+--------+
                                       |
                                       v
                              +-----------------+
                              |   web-api Pod   |
                              +--+-----------+--+
                                 |           |
                                 | gRPC      | AMQP
                                 v           v
                      +-----------------+ +----------+
                      |   notification  | | rabbitmq |
                      | (ClusterIP svc) | |          |
                      +-----------------+ +----+-----+
                                               ^
                                               | AMQP
                                               |
                                        +-------------+
                                        |   worker    |   (no Service)
                                        +-------------+

Path matching (longest-prefix wins):

Path Backend Protocol
/grpc/pubsub/... notification:8080 h2c (gRPC)
/grpc/... web-api:8080 h2c (gRPC)
/... (catch-all) web-api:8081 HTTP/1 (REST, /connect/token, Razor, SignalR)

Storage

  • rabbitmq-data — a single 10 Gi ReadWriteOnce (RWO) volume, used only by the bundled RabbitMQ broker.
  • Images, scanned documents, and label templates are stored in S3 object storage, so there are no in-cluster image volumes and no RWX (ReadWriteMany) StorageClass is required.

What you provide

  • A SQL Server instance reachable from the cluster.
  • S3 (or S3-compatible) object storage plus an access key / secret key pair — for images, scanned documents, and label templates.
  • Registry pull credentials for prima-registry.fortelinea.com (provided by Fortelinea).
  • A DNS hostname for the public Route (or accept the OpenShift-assigned hostname).
  • (Optional) an external RabbitMQ broker, if you'd rather not run the bundled one — see External RabbitMQ broker.

Prerequisites

Requirement Notes
OpenShift 4.11+ oc CLI logged in. 4.11 is the minimum version where the appProtocol: h2c Service port hint is honored by the router (required for gRPC). For OCP 4.9–4.10, additional manual router config is needed — contact Fortelinea.
HTTP/2 enabled on the IngressController One-time cluster-admin action so gRPC Routes work end-to-end. See Cluster prerequisite below.
SQL Server TCP-reachable from the cluster. Customer-managed. Connection string goes into the Secret.
S3 object storage An S3 bucket (AWS S3 or any S3-compatible store) for images, scanned documents, and label templates, plus an access key / secret key pair. Prima writes images here instead of to in-cluster volumes, so no RWX StorageClass is required. Bucket names and region are set on the Deployments; the keys go into the Secret.
(default) StorageClass Only the bundled RabbitMQ broker needs a PVC, and it is RWO — satisfied by virtually any StorageClass, so the cluster default is fine. If you use an external RabbitMQ broker you need no PVCs at all.
Registry credentials Username + access token for prima-registry.fortelinea.com, provided by Fortelinea.
TLS certificate Optional — supply a custom cert for the Routes, or let OpenShift use its default ingress cert. WPF clients must trust the cert chain.

One-time setup

Deploying Prima involves two roles: a cluster administrator (for the one-time HTTP/2 step described below) and an application deployer (for everything else). These can be the same person if you have cluster-admin rights.

1. Create a project

oc new-project prima

2. Enable HTTP/2 on the IngressController (cluster-admin required)

The Prima WPF apps connect to the gRPC Route over HTTPS/HTTP-2. The default OpenShift IngressController does not have HTTP/2 enabled until you set the annotation below. This is a one-time, cluster-wide action a cluster admin must perform before applying 60-web-api.yaml:

oc -n openshift-ingress-operator annotate \
  ingresscontroller/default \
  ingress.operator.openshift.io/default-enable-http2=true

The default ingress router will roll automatically. Verify after rollout:

oc -n openshift-ingress get pods -l ingresscontroller.operator.openshift.io/deployment-ingresscontroller=default

If your cluster has multiple custom IngressControllers and Prima will be exposed through a non-default one, apply the same annotation to that IngressController.

3. Create the application secrets

cp 20-secrets.example.yaml 20-secrets.yaml
# Edit 20-secrets.yaml — replace every CHANGE_ME value.
oc apply -f 20-secrets.yaml
Do not commit 20-secrets.yaml. The .example suffix on the template is intentional; add 20-secrets.yaml to your own ignore list if you keep this directory under version control.

4. Create the persistent volume claim

Only the bundled RabbitMQ broker needs persistent storage (a single RWO PVC). Images and documents go to S3, so there are no image PVCs.

oc apply -f 30-pvcs.yaml

Verify it reaches Bound:

oc get pvc -w

If it stays Pending, check oc describe pvc rabbitmq-data — usually no default StorageClass is set on the cluster.

Using an external RabbitMQ broker? Skip this step and don't apply 40-rabbitmq.yaml — Prima then needs no PVCs at all. See External RabbitMQ broker.

Deploy

The files use numeric prefixes so oc apply -f . would almost work, but dbinit must complete before the long-running services start. Apply in two stages:

# Stage 1 — broker + database migrations
# (Using an external RabbitMQ broker? Skip the 40-rabbitmq.yaml line.)
oc apply -f 40-rabbitmq.yaml
oc apply -f 50-dbinit-job.yaml
oc wait --for=condition=complete --timeout=10m job/prima-dbinit

# Stage 2 — application services
oc apply -f 60-web-api.yaml
oc apply -f 70-worker.yaml
oc apply -f 80-notification.yaml

The web-api / worker / notification pods include an initContainer that polls RabbitMQ before the main container starts, so order between those three doesn't matter — they'll wait for each other naturally.

Check overall status:

oc get pods,svc,route

Set the public hostname

All three Routes share one host: value. Search-and-replace the placeholder before applying — the same hostname must appear in 60-web-api.yaml (two Routes) and 80-notification.yaml (one Route):

sed -i 's/prima\.example\.com/prima.your-org.com/g' 60-web-api.yaml 80-notification.yaml

Get all three Routes once applied:

oc get routes -o custom-columns=NAME:.metadata.name,HOST:.spec.host,PATH:.spec.path,SERVICE:.spec.to.name,PORT:.spec.port.targetPort

You should see three rows on the same host with paths <none>, /grpc, and /grpc/pubsub.

Configuring object storage (S3)

Prima stores slide / specimen / cassette images, scanned documents, and label templates in S3-compatible object storage rather than in-cluster volumes. Configuration lives on the web-api and worker Deployments; the access keys come from the Secret.

Env var File Default Notes
ImageStorage__File__RootDirectory 60-, 70- "" Empty string disables the on-disk backend, forcing the S3 backend. Leave empty.
ImageStorage__S3__BucketName 60-, 70- prima-images Bucket for slide/specimen/cassette images + scanned docs.
LabelTemplateStorage__S3__BucketName 60- prima-label-templates Bucket for label templates (web-api only).
StorageCredentialProfiles__0__Region 60-, 70- us-east-1 Region for the buckets.
StorageCredentialProfiles__0__Name / ImageStorage__S3__CredentialProfile / LabelTemplateStorage__S3__CredentialProfile 60-, 70- AWS Logical name tying the storage backends to credential profile 0. Keep them all matching.
aws-access-key-id / aws-secret-access-key 20-secrets.yaml Access key pair, surfaced as StorageCredentialProfiles__0__AccessKeyId / __SecretAccessKey.
S3Settings__TimeoutSeconds / S3Settings__MaxRetryAttempts 60-, 70- 30 / 3 Client timeout and retry tuning.
S3-compatible stores (MinIO, Ceph RGW, Wasabi, etc.): these settings target AWS S3 by default. If you use a non-AWS endpoint, you'll also need a service-URL override — contact Fortelinea for the exact setting that matches your Prima version.

LDAP / Active Directory authentication (optional)

By default Prima uses its built-in (local) authentication. To authenticate users against your directory instead, set the LDAP variables on the web-api Deployment. A commented-out block is already present in 60-web-api.yaml — uncomment and fill it in:

Env var Example Notes
LdapConfiguration__AuthenticationType Ldap Prima (local, default), Ldap, or Okta. Set to Ldap to enable directory auth.
LdapConfiguration__Host dc01.example.com Domain-controller / LDAP server hostname.
LdapConfiguration__Port 636 636 for LDAPS, 389 for plain LDAP.
LdapConfiguration__Protocol LDAPS LDAP or LDAPS.
LdapConfiguration__Domain example.com AD domain.
LdapConfiguration__UserStore ou=Users,dc=example,dc=com Base DN for the user search.
LdapConfiguration__AuthType Negotiate Negotiate, Basic, or Anonymous.
LdapConfiguration__CertificateVerificationMethod Default Default, SkipVerification, or CustomCertificate (with LdapConfiguration__X509CertificatePath).

These values are not secrets (the directory connection uses the integrated AuthType above rather than a stored bind password), so they live as plain value: entries on the Deployment. This is authentication configuration only — it controls how users sign in, not Prima's in-app authorization/roles, which remain database-driven.

External RabbitMQ broker

The bundled 40-rabbitmq.yaml runs RabbitMQ in-cluster and is the simplest option. If you'd rather supply your own broker — a dedicated server, a managed service such as AWS Amazon MQ for RabbitMQ, or a container you operate — point Prima at it instead:

  1. Don't apply 40-rabbitmq.yaml, and skip the rabbitmq-data PVC in 30-pvcs.yaml (Prima then needs no PVCs at all).
  2. On the web-api, worker, and notification Deployments, set the broker connection:
    - name: RabbitMq__Host
      value: my-broker.example.com      # or the Amazon MQ endpoint hostname
    - name: RabbitMq__Port
      value: "5672"                     # 5671 for AMQPS/TLS
    - name: RabbitMq__VirtualHost
      value: "/"
    - name: RabbitMq__Username
      value: prima
    # RabbitMq__Password already comes from the prima-secrets "rabbitmq-password" key
  3. The wait-for-rabbitmq initContainers poll rabbitmq:5672 by name. Update them to your broker's host:port (or remove them if the broker is always reachable) so the pods don't block on startup:
    - until nc -z my-broker.example.com 5672; do echo "waiting for rabbitmq"; sleep 2; done

Set RabbitMq__Host consistently on all three client Deployments. The credentials must be valid on your broker; with Amazon MQ, create the user and vhost in the broker console first.

Upgrading

Replace :latest in the four image references with a pinned tag before applying. Recommended upgrade order:

# 1. Run migrations for the new version
oc delete job prima-dbinit
oc apply -f 50-dbinit-job.yaml   # ensure image tag is updated
oc wait --for=condition=complete --timeout=10m job/prima-dbinit

# 2. Roll the services
oc apply -f 60-web-api.yaml
oc apply -f 70-worker.yaml
oc apply -f 80-notification.yaml

The Deployments use a Recreate rollout strategy (single replica each). Expect ~30s of API downtime per service. Now that images live in S3 rather than on an RWO volume, web-api can be switched to RollingUpdate and scaled horizontally if you want zero-downtime upgrades — see the replica-count note in the customization checklist.

Customization checklist

Setting File Default Tune for
S3 buckets / region 60-, 70- prima-images, prima-label-templates, us-east-1 Your object-storage layout. See Configuring object storage.
rabbitmq-data PVC size 30-pvcs.yaml 10 Gi Broker message backlog. Only relevant to the bundled broker.
Memory limits 40-, 60-, 70-, 80- 2 / 4 / 2 / 2 Gi Worker memory scales with concurrent slide-image processing.
Shared Route hostname 60-web-api.yaml ×2, 80-notification.yaml ×1 prima.example.com Single DNS hostname for all client traffic. All three Routes MUST match.
TLS certificate All three Routes OpenShift default ingress cert Add tls.key / tls.certificate / tls.caCertificate (same cert on all three Routes) if you bring your own.
LDAP / AD auth 60-web-api.yaml local (Prima) auth Uncomment the LdapConfiguration__* block to authenticate against your directory. See LDAP / Active Directory.
Replica count 60-, 70-, 80- 1 each Web-api can scale horizontally (images are in S3 — switch its strategy to RollingUpdate to scale out). Worker scales when behind on queue depth. Notification is stateful — keep at 1 unless you understand the gRPC stream semantics.

Configuring the Prima WPF apps

The Workstation, Pathologist, LabManager, ControlPanel, and LabelTemplateCreator clients all connect to the gRPC API. They use one URL for both web-api gRPC and notification gRPC streams — the path-based fan-out at the Route layer takes care of the rest:

"PrimaGrpcApiUrl": "https://prima.your-org.com"

REST clients (browser, scripts, third-party integrations) use the same hostname — /api/v1/..., /api/v2/..., /connect/token all route to the REST backend via the catch-all.

The WPF client uses Grpc.Net.Client with a standard HTTPS channel — no Http2UnencryptedSupport switch and no extra TLS configuration is needed, because the wire is HTTP/2 over TLS (the router terminates TLS and forwards h2c to the pod internally). Clients must trust the certificate chain presented by the Routes; if you bring your own cert, distribute the CA to client machines through your normal workstation provisioning.