OpenShift Production Deployment
This guide walks you through deploying the complete FileGrant infrastructure on OpenShift using a Helm chart. It covers architecture, prerequisites, step-by-step installation, DNS configuration, scaling, monitoring, security best practices, and uninstallation.
Architecture Overview
The Helm chart deploys three groups of components: core application services, supporting observability services, and OpenShift-specific infrastructure resources.
Core Services
- FileGrant Frontend — Web application frontend, pulled from Azure Container Registry.
- CGSignalR — SignalR service for real-time communication.
- RabbitMQ — Message broker with AMQPS support.
- FileGrantAPI — Main FileGrant REST API.
- FileGrantPdfProcessing — PDF processing service.
- FGLicense — License management service.
Supporting Services
- Loki — Centralized log aggregation.
- Alloy — Log collection agent deployed as a DaemonSet. Alloy replaces Promtail, which has reached end-of-life.
- Grafana — Dashboard for log visualization.
OpenShift Infrastructure
- Routes — HTTP/HTTPS traffic management with TLS edge termination.
- SecurityContextConstraints (SCC) — Permissions for the Alloy DaemonSet.
- NetworkPolicies — Security rules governing inter-pod communication.
Prerequisites
Before you begin, make sure you have the following in place:
- OpenShift Cluster — version 4.x or higher.
- oc CLI — configured and authenticated against your cluster.
- Helm 3.x — installed locally.
- DNS Domains — configured and pointing to the OpenShift Router IP.
- Azure Container Registry access — a service principal with pull permissions (used to create an imagePullSecret).
Verify the Cluster
Run the following commands to confirm your cluster is reachable and the ingress controller is operational:
oc status
oc version
oc get pods -n openshift-ingressGet the OpenShift Router IP
You will need the Router IP for DNS configuration later. Retrieve it with:
oc get svc -n openshift-ingress router-default -o jsonpath='{.status.loadBalancer.ingress[0].ip}'Installation
Working directory
helm commands in this guide assume you are inside the helm-chart/ directory.The installation requires 3 phases due to sequential dependencies:
- FileGrantAPI, RabbitMQ, and FGLicense must start first (core infrastructure).
- RabbitMQ must be configured (virtual host and first user) before other services can connect.
- FGLicense must perform the initial EEVMS variable setup in storage — without these variables, EEVMS certificates cannot be authorized.
- Only after the initial setup can you generate certificates for CGSignalR and PDFProcessing and authorize the EEVMS keys.
Phase 1: Bootstrap — FileGrantAPI + RabbitMQ + FGLicense
│
├─ Prepare values-production.yaml (without EEVMS certificates)
├─ helm install (only FileGrantAPI, RabbitMQ, FGLicense)
├─ Wait for pods to be Running
├─ Configure RabbitMQ (virtual host + first user)
└─ FGLicense: initial EEVMS variable setup in storage
│
Phase 2: Generate certificates and authorize EEVMS
│
├─ Generate PFX certificates for CGSignalR and PDFProcessing
├─ Update values-production.yaml with certificates
└─ Authorize EEVMS keys
│
Phase 3: Enable all services
│
├─ helm upgrade (CGSignalR and PDFProcessing enabled)
├─ CGSignalR and PDFProcessing start and load secrets from EEVMS
└─ Final verificationConfigure Azure Container Registry (ACR) Access
All service images are hosted on Azure Container Registry. Create a project and an imagePullSecret so OpenShift can pull the images:
oc new-project filegrant
oc create secret docker-registry acr-secret \
--docker-server=cybergrantregistry-cuash9ckhse5gcha.azurecr.io \
--docker-username=<SERVICE_PRINCIPAL_ID> \
--docker-password=<SERVICE_PRINCIPAL_PASSWORD> \
-n filegrantCreate the RabbitMQ TLS Secret
RabbitMQ TLS is enabled by default (rabbitmq.tls.enabled: true). You must create a TLS secret before installing the chart.
oc create secret tls rabbitmq-tls \
--cert=path/to/tls.crt \
--key=path/to/tls.key \
-n filegrantopenssl req -x509 -nodes -days 365 \
-newkey rsa:2048 \
-keyout rabbitmq-tls.key \
-out rabbitmq-tls.crt \
-subj "/CN=rabbitmq.filegrant.svc.cluster.local"
oc create secret tls rabbitmq-tls \
--cert=rabbitmq-tls.crt \
--key=rabbitmq-tls.key \
-n filegrantDisabling TLS
rabbitmq.tls.enabled: false in your values-production.yaml.Self-signed certificates
Installation Steps
Prepare values-production.yaml (initial)
Copy the default values file and customize it for your environment. In this phase, EEVMS certificates for CGSignalR and PDFProcessing are not needed yet — they will be added in Phase 2.
cp values.yaml values-production.yamlEdit the following fields in
values-production.yaml:values-production.yaml (initial fields)global: domain: base: "yourdomain.com" editorApiKey: "<editor-api-key>" grafana: auth: adminPassword: "secure-password" filegrantapi: secrets: masterCertificateBase64: "<base64-encoded-certificate>" masterCertificatePassword: "<certificate-password>" secretsAzureConnectionString: "<azure-blob-connection-string>" fglicense: secrets: monitoringApiKey: "<monitoring-api-key>" grafanaToken: "<grafana-service-account-token>" filegrantfe: env: pdfProcessingBaseUrl: "<value>" officeExcelIconUrl: "<value>" officeWordIconUrl: "<value>" officeWopisourceUrl: "<value>" officeWopitestUrl: "<value>" officePowerpointUrl: "<value>" officeExcelUrl: "<value>" officeWordUrl: "<value>" stripePublicKey: "<value>" cryptoKey: "<value>" remotegrantAgentBaseUrl: "<value>" apiBaseUrl: "<value>" azureResourcesContainer: "<value>" websocketUrl: "<value>" reactPdfLicenseKey: "<value>"Do not commit secrets
Never commitvalues-production.yamlwith real passwords or secrets to version control.Assign the SCC for Alloy
Alloy (the log collector) requires a privileged SecurityContextConstraints because it needs access to node logs at
/var/log. The SCC is automatically created by the chart. Verify it has been applied:oc get scc alloy-sccInstall the Chart (Phase 1 — Bootstrap)
Install with only FileGrantAPI, RabbitMQ, and FGLicense enabled. CGSignalR and PDFProcessing are disabled because the EEVMS variables do not exist yet.
Phase 1: Install core serviceshelm install filegrant . \ --namespace filegrant \ --create-namespace \ -f values-production.yaml \ --set cgsignalr.enabled=false \ --set filegrantpdfprocessing.enabled=falseWait for
filegrantapi,rabbitmq, andfglicenseto reach the Running state withREADY 1/1:oc get pods -n filegrant -wConfigure RabbitMQ
RabbitMQ requires the virtual host and the first application user to be configured before other services can connect.
Access the RabbitMQ Management UI:
Access RabbitMQ Management# Via Route (if enabled) open https://rabbitmq.yourdomain.com # Or via port-forward oc port-forward -n filegrant svc/rabbitmq 15672:15672 # Then open http://localhost:15672Log in with the credentials from
values-production.yaml(default username:admin-rmq), then:- Create the Virtual Host: go to Admin > Virtual Hosts, create the vhost required by FileGrant (e.g.,
/filegrant). - Create the application user: go to Admin > Users, create the user that services will use to connect to RabbitMQ.
- Set permissions: assign the user permissions on the virtual host (configure, write, read:
.*).
- Create the Virtual Host: go to Admin > Virtual Hosts, create the vhost required by FileGrant (e.g.,
FGLicense — Initial EEVMS Variable Setup
FGLicense must perform the initial setup to create all required variables in EEVMS storage. Without this step, EEVMS certificates cannot be authorized because the variables do not exist yet.
Access FGLicense:
Access FGLicense# Via Route (if enabled) open https://license.yourdomain.com # Or via port-forward oc port-forward -n filegrant svc/fglicense 5003:8080 # Then open http://localhost:5003Complete the initial setup. FGLicense will:
- Create all required variables in EEVMS storage.
- Set initial configuration values (connection strings, JWT keys, etc.).
- Prepare the storage for EEVMS certificate authorization.
Mandatory step
This step is mandatory. If skipped, the EEVMS authorization script (Step 8) will fail withnot_founderrors for all variables.Generate EEVMS Certificates
Now that the variables exist in EEVMS storage, generate PFX certificates for CGSignalR and PDFProcessing. These certificates are used for RSA authentication with FileGrantAPI.
Generate certificates./scripts/generate-eevms-certs.sh --password "YOUR_SECURE_PASSWORD"This creates the following files inside the
certs/directory:cgsignalr.pfx/cgsignalr.b64/cgsignalr-pubkey.pemfilegrantpdfprocessing.pfx/filegrantpdfprocessing.b64/filegrantpdfprocessing-pubkey.pem
Update values-production.yaml with Certificates
Add the EEVMS client certificate sections to
values-production.yaml:Add to values-production.yamlcgsignalr: eevms: apiUrl: "http://filegrantapi:8080" apiSecret: "<contents-of-certs/cgsignalr.b64>" apiSecretPassword: "YOUR_SECURE_PASSWORD" filegrantpdfprocessing: eevms: apiUrl: "http://filegrantapi:8080" apiSecret: "<contents-of-certs/filegrantpdfprocessing.b64>" apiSecretPassword: "YOUR_SECURE_PASSWORD"Authorize EEVMS Keys
Authorize the public keys so CGSignalR and PDFProcessing can read the secrets from EEVMS:
Authorize EEVMS public keys./scripts/authorize-eevms-keys.sh \ --api-url "https://filegrantapi.yourdomain.com" \ --api-key "<editor-api-key>"Verify the output shows all variables as
authorized. You should see CGSignalR with 11 authorized variables and PDFProcessing with 10.Variables not found?
If some variables show asnot_found, go back to Step 5 and verify that FGLicense has completed the initial setup.Enable All Services (Phase 3)
Now that the keys are authorized and the certificates are in
values-production.yaml, enable CGSignalR and PDFProcessing:Phase 3: Enable remaining serviceshelm upgrade filegrant . \ --namespace filegrant \ -f values-production.yamlThis starts the remaining services:
- CGSignalR — connects to FileGrantAPI via EEVMS and loads 11 secrets.
- PDFProcessing — connects to FileGrantAPI via EEVMS and loads 10 secrets.
Verify the Deployment
Run the following commands to confirm all pods are running, all routes are created, and the services are logging as expected:
Verify deployment healthoc get pods -n filegrant oc get routes -n filegrant oc get scc alloy-scc oc logs -n filegrant -l app=cgsignalr --tail=10 oc logs -n filegrant -l app=filegrantpdfprocessing --tail=10 oc logs -n filegrant -l app=fglicense --tail=10All pods should be in
Runningstate after a few minutes.
Accessing Services
Public Services (via Route)
The following services are exposed externally through OpenShift Routes with TLS edge termination. Replace <domain> with your configured base domain.
| Service | URL | Notes |
|---|---|---|
| FileGrant Frontend | https://filegrant.<domain> | Web application |
| CGSignalR | https://signalr.<domain> | WebSocket Secure |
| RabbitMQ Management | https://rabbitmq.<domain> | Management UI |
| FileGrantAPI | https://filegrantapi.<domain> | REST API |
| PDF Processing | https://pdfprocessing.<domain> | Processing API |
| FGLicense | https://license.<domain> | License management (disabled by default) |
Internal Services (Port-Forward)
Grafana and FGLicense (when its Route is disabled) are accessible through port-forward:
# Grafana (logging)
oc port-forward -n filegrant svc/grafana 3000:3000
# http://localhost:3000
# FGLicense (if Route is disabled)
oc port-forward -n filegrant svc/fglicense 5003:8080
# http://localhost:5003Default Credentials
| Service | Default Username | Default Password |
|---|---|---|
| Grafana | admin | See values.yaml |
| RabbitMQ | admin-rmq | See values.yaml |
Change default passwords
values.yaml are only intended as placeholders.DNS Configuration
Configure A-type DNS records pointing to the OpenShift Router IP for each of the following subdomains:
filegrant.<domain>signalr.<domain>rabbitmq.<domain>filegrantapi.<domain>pdfprocessing.<domain>
Router IP
Updates and Rollback
Upgrading the Release
To apply configuration changes or deploy new image versions, run a Helm upgrade:
helm upgrade filegrant . -f values-production.yamlUpdate a Specific Service
To update the image tag for a single service without changing anything else:
helm upgrade filegrant . \
--set signalrserviceapi.image.tag=v1.2.3 \
-f values-production.yamlRolling Back
If an upgrade causes issues, roll back to the previous revision or to a specific revision number:
# Roll back to the previous revision
helm rollback filegrant
# Roll back to a specific revision (e.g., revision 2)
helm rollback filegrant 2Scaling
Manual Scaling
Scale any deployment manually using oc scale or via Helm:
# Via oc
oc scale deployment signalrserviceapi -n filegrant --replicas=3
# Or via Helm
helm upgrade filegrant . \
--set signalrserviceapi.replicaCount=3 \
-f values-production.yamlAutoscaling (HPA)
You can enable Horizontal Pod Autoscaling for any service through the values file. For example, to autoscale the SignalR service:
signalrserviceapi:
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80Monitoring and Logging
Accessing Logs with Grafana
The chart deploys Loki for log aggregation, Alloy for log collection, and Grafana for visualization. Access Grafana via port-forward (see above), then navigate to Explore and select Loki as the data source.
Here are some useful LogQL queries to get started:
# FileGrantAPI logs
{job="filegrantapi"}
# Error logs
{namespace="filegrant"} |~ "(?i)error"
# Specific pod logs
{pod="filegrantapi-xxx-yyy"}OpenShift Metrics
# Pod CPU and memory
oc adm top pods -n filegrant
# Namespace events
oc get events -n filegrant --sort-by='.lastTimestamp'Backup and Disaster Recovery
RabbitMQ Backup
Export the RabbitMQ definitions (users, vhosts, queues, exchanges, bindings) for backup:
oc exec -n filegrant rabbitmq-0 -- \
rabbitmqctl export_definitions /tmp/definitions.json
oc cp filegrant/rabbitmq-0:/tmp/definitions.json ./rabbitmq-backup.jsonSecurity
Best Practices
- Change all default passwords before going to production.
- Do not commit
values-production.yamlwith real passwords or secrets to version control. - NetworkPolicies are enabled by default and restrict inter-pod communication to only what is necessary.
- SecurityContextConstraints are configured for the minimum required privileges.
Secrets Management
The following table summarizes the Kubernetes secrets created by the chart and the keys they contain. All secret values default to CHANGE_ME in values.yaml. Override them using --set or a separate values-production.yaml.
| Secret | Service | Keys |
|---|---|---|
rabbitmq-secret | RabbitMQ | username, password |
grafana-secret | Grafana | admin-user, admin-password |
filegrantapi-secret | FileGrantAPI | master-certificate-base64, master-certificate-password, secrets-azure-connection-string, editor-api-key |
cgsignalr-eevms-secret | CGSignalR | filegrant-api-secret, filegrant-api-secret-password |
filegrantpdfprocessing-eevms-secret | PDFProcessing | filegrant-api-secret, filegrant-api-secret-password |
fglicense-secret | FGLicense | monitoring-api-key, grafana-token, editor-api-key |
Shared key
editor-api-key is shared between FileGrantAPI and FGLicense. It is configured once in global.editorApiKey and referenced by both secrets.SecurityContextConstraints
- alloy-scc — Allows the Alloy DaemonSet to access node logs in privileged mode.
- All other services run with OpenShift's default
restrictedSCC (random UID, no privilege escalation).
Container Ports
All application services use port 8080 (not 80) to comply with OpenShift's restricted SCC, which does not allow binding to ports below 1024.
Troubleshooting
Routes Are Not Working
# Check Route status
oc describe route -n filegrant
# Check the OpenShift Router
oc get pods -n openshift-ingress
# Check Router logs
oc logs -n openshift-ingress -l ingresscontroller.operator.openshift.io/deployment-ingresscontroller=defaultPods Are Not Starting
# Check pod logs
oc logs -n filegrant <pod-name>
# Describe the pod to see events
oc describe pod -n filegrant <pod-name>
# Check available resources
oc adm top nodesSCC Errors (SecurityContextConstraints)
# Verify SCCs assigned to the service account
oc get scc
# Verify that alloy-scc exists
oc describe scc alloy-scc
# Check Alloy pod events
oc describe pod -n filegrant -l app=alloyAlloy Is Not Collecting Logs
# Check Alloy logs
oc logs -n filegrant -l app=alloy
# Verify that Alloy can see targets
oc exec -n filegrant -l app=alloy -- \
wget -qO- http://localhost:12345/targets
# Verify that Loki is reachable
oc exec -n filegrant loki-0 -- wget -qO- http://localhost:3100/readyUninstallation
To completely remove the FileGrant deployment and clean up all associated resources:
# Remove the release
helm uninstall filegrant -n filegrant
# (Optional) Remove the project
oc delete project filegrantPersistent data
oc delete pvc --all -n filegrant