Skip to content

Deployment Guide

openwcs-docs-agent edited this page Jun 11, 2026 · 8 revisions

Installation & Deployment

How to install openWCS and deploy it — on a local Linux box, a cloud VM, or as managed containers on AWS, Google Cloud, and Microsoft Azure.

What ships today. openWCS is built from source. A Dockerfile for every service and a ready-to-run platform/docker-compose.yml are committed, but the project does not yet publish prebuilt images, Helm charts, or Terraform. The cloud sections below are recipes: build the images yourself, push them to your registry, and wire them up with the platform's native primitives. Contributions of charts / IaC are very welcome — see Contributing.

For a developer build-and-run loop, see Getting Started. This page is about standing the system up for real.


Contents


What you're deploying

Tier Components Notes
Edge gateway (8080) The only service that needs to be publicly reachable. Routes /api/<service>/**, validates JWTs (optional), forwards identity headers.
Domain services (Spring Boot, JRE 21) master-data, inventory, process-engine, order-management, allocation, flow-orchestrator, txlog, iam, notification, integration-host, integration-sap, integration-manhattan Stateless HTTP; all reach one PostgreSQL database (schema-per-service) and Kafka. Reach each other by hostname over HTTP.
Device adapters (Go) conveyor, asrs, amr-geekplus, autostore, conveyor-sniffer Talk equipment protocols ↔ the uniform device contract. The sniffer also needs L2 reachability to the conveyor network it learns from.
Equipment emulator (Go) equipment-emulator Single emulator for all device families (port 9097). flow-orchestrator routes device tasks here when HARDWARE_EMULATOR_ENABLED is ON. Stateless; no DB/Kafka/hardware connection needed.
UI (React/Vite) ui A static bundle (npm run builddist/). Serve it from any static host and point it at the gateway.
Infrastructure PostgreSQL 16 · Apache Kafka · Keycloak 25 (auth) · (optional) Schema Registry In production, prefer managed Postgres + Kafka.

Key wiring (env vars). Every Spring service takes:

SPRING_DATASOURCE_URL=jdbc:postgresql://<pg-host>:5432/openwcs
SPRING_DATASOURCE_USERNAME=openwcs
SPRING_DATASOURCE_PASSWORD=<secret>
SPRING_KAFKA_BOOTSTRAP_SERVERS=<broker:9092>     # services that use Kafka

The gateway is told where the services live via OPENWCS_URI_<SERVICE> (e.g. OPENWCS_URI_INVENTORY=http://inventory:8082); services that call each other use OPENWCS_<TARGET>_BASE_URL. In Compose those are hostnames; in the cloud they become your platform's internal DNS names (Cloud Map / Container Apps / k8s Service). See the committed platform/docker-compose.yml for the full, authoritative set.

Ports are listed in Getting Started.


1 · Build & publish the images

Every deployment target consumes the same images, so build them once and push to your registry. Prerequisites on the build host: JDK 21, Docker, Go 1.25+, Node 20+.

git clone https://github.com/brettljausn-ai/openwcs.git && cd openwcs

# Pick your registry prefix:
#   GHCR:  ghcr.io/<org>
#   ECR:   <acct>.dkr.ecr.<region>.amazonaws.com
#   GAR:   <region>-docker.pkg.dev/<project>/<repo>
#   ACR:   <name>.azurecr.io
REG=ghcr.io/your-org
TAG=$(git rev-parse --short HEAD)

# Java jars (the Dockerfiles COPY build/libs/*.jar)
./gradlew bootJar

# Java services — the module directory is the build context
for m in gateway \
  services/master-data services/inventory services/process-engine services/order-management \
  services/allocation services/flow-orchestrator services/txlog services/iam services/notification \
  services/integration-host services/integration-sap services/integration-manhattan; do
  name="openwcs-$(basename "$m")"
  docker build -t "$REG/$name:$TAG" "$m" && docker push "$REG/$name:$TAG"
done

# Go adapters — multi-stage, built from source
for a in conveyor asrs amr-geekplus autostore conveyor-sniffer; do
  docker build -t "$REG/openwcs-$a-adapter:$TAG" "services/adapters/$a" \
    && docker push "$REG/openwcs-$a-adapter:$TAG"
done

# Single equipment emulator (all families)
docker build -t "$REG/openwcs-equipment-emulator:$TAG" "services/equipment-emulator" \
  && docker push "$REG/openwcs-equipment-emulator:$TAG"

# UI — static bundle (host it on a static service, or bake your own nginx image)
( cd ui && npm ci && npm run build )   # → ui/dist

Authenticate to your registry first (docker login ghcr.io, aws ecr get-login-password …, gcloud auth configure-docker …, or az acr login …).


2 · Local Linux server

Smallest real install: one box running everything via Docker Compose. Good for a pilot, a single line, or an on-prem edge server next to the equipment.

# Debian/Ubuntu — install Docker Engine + the compose plugin
sudo apt-get update && sudo apt-get install -y ca-certificates curl
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker "$USER" && newgrp docker

git clone https://github.com/brettljausn-ai/openwcs.git && cd openwcs

./gradlew bootJar                                   # JDK 21 required to build
docker compose -f platform/docker-compose.yml --profile apps up --build -d

Or one commandscripts/setup-demo.sh does all of the above on a fresh Ubuntu 22.04/24.04 box (installs Docker + JDK 21, clones to /opt/openwcs, builds, starts):

curl -fsSL https://raw.githubusercontent.com/brettljausn-ai/openwcs/main/scripts/setup-demo.sh | sudo bash
# add --auto-deploy (clone first, then run it) to also install the auto-deploy timer

This starts PostgreSQL, Kafka (+ZooKeeper), Keycloak (imports the openwcs realm), Schema Registry, every Java service, the Go adapters, and the gateway. Check health and watch logs:

docker compose -f platform/docker-compose.yml ps
curl localhost:8080/actuator/health        # gateway
docker compose -f platform/docker-compose.yml logs -f gateway

Make it production-ish on one host:

  • Run on boot — wrap the compose project in a systemd unit (ExecStart=docker compose … up, ExecStop=docker compose … down, Restart=always), or add restart: unless-stopped to the services.
  • Auto-deploy on main — keep the box in sync with the repo automatically, either with a poll-based systemd timer or a CI-gated self-hosted GitHub Actions runner. Both ship in deploy/ and reuse scripts/deploy.sh (fast-forward → rebuild → recompose, no-op when unchanged).
  • TLS + a real hostname — put Caddy or nginx in front, terminating HTTPS and proxying to gateway:8080. Only the gateway (and the UI host) should be exposed; keep Postgres, Kafka, Keycloak, and the services on the internal Docker network.
  • Persistence — the pgdata volume holds your system of record. Put it on durable storage and back it up (pg_dump/pg_basebackup); snapshot Keycloak's data too.
  • Externalise the DB (optional) — point SPRING_DATASOURCE_URL at a separate PostgreSQL host instead of the in-compose one for easier backups/HA.
  • Security — set OPENWCS_SECURITY_ENABLED=true and change all default passwords (Postgres, Keycloak admin, realm users). See Security.
  • Serve the UI — the --profile apps stack already builds and serves the UI with nginx on host :80/:443 (it proxies /api → gateway), so browse the demo at http://<host>/. For a real HTTPS cert (no browser warning), point DNS at the server and run the built-in helper — pass a comma-separated list to cover several domain names with a single SAN cert:
    sudo /opt/openwcs/scripts/issue-cert.sh app.openwcs.ai,openwcs.brettljausn.ai you@example.com
    The script runs certbot, installs the cert, and hot-reloads nginx. Add a daily cron to auto-renew (use the same domain list — a shorter list would drop names from the cert):
    # crontab -e  (root)
    0 3 * * * /opt/openwcs/scripts/issue-cert.sh app.openwcs.ai,openwcs.brettljausn.ai you@example.com >> /var/log/openwcs-cert.log 2>&1
    For production, put your own reverse proxy / TLS in front, or host ui/dist separately and point it at the gateway URL.

3 · A cloud VM (any provider)

An EC2 instance, GCE VM, Azure VM, or a droplet is just §2 on rented hardware — the fastest way to get a public, internet-reachable instance.

  1. Provision a VM (start with 4 vCPU / 8–16 GB RAM; the full stack with Kafka is the heavy part) running a current Ubuntu/Debian.

  2. Open only 443 (and 80 for the ACME challenge) in the firewall / security group. SSH from your IP only. Do not expose 5432/9092/8180.

  3. Follow §2. Use Caddy for automatic Let's Encrypt TLS:

    wcs.example.com {
      reverse_proxy /api/* gateway:8080
      reverse_proxy /*     ui:80          # or serve ui/dist directly
    }
  4. For anything beyond a pilot, move the database to the provider's managed PostgreSQL and point SPRING_DATASOURCE_URL at it — you get backups, failover, and patching for free.

When you outgrow a single VM (HA, autoscaling, rolling deploys), graduate to the managed-container options below.


4 · AWS — containers (ECS Fargate)

Run each service as a Fargate task; use managed data services. (EKS is an alternative if you prefer Kubernetes — see §5's GKE recipe, which translates directly.)

Managed building blocks

Need AWS service
Images ECR (one repo per image from §1)
PostgreSQL RDS for PostgreSQL 16 — one instance, database openwcs
Kafka Amazon MSK (native Kafka) — set SPRING_KAFKA_BOOTSTRAP_SERVERS to the broker string
Service-to-service DNS ECS Service Connect / Cloud Map private namespace (e.g. *.openwcs.local)
Public entry ALB → the gateway service only (target port 8080, health check /actuator/health)
Secrets Secrets Manager / SSM Parameter Store → injected as task env vars
UI S3 + CloudFront (upload ui/dist)

Shape of it

  1. aws ecr create-repository for each image; push (§1) with REG=<acct>.dkr.ecr.<region>.amazonaws.com.

  2. Create the RDS instance and (optionally) MSK cluster in a VPC with private subnets.

  3. Create an ECS cluster and a task definition per service. Each task's env mirrors the Compose file, but hostnames become Service Connect DNS names:

    SPRING_DATASOURCE_URL = jdbc:postgresql://<rds-endpoint>:5432/openwcs
    SPRING_KAFKA_BOOTSTRAP_SERVERS = <msk-bootstrap-brokers>
    OPENWCS_URI_INVENTORY = http://inventory.openwcs.local:8082      # on the gateway task
    OPENWCS_ALLOCATION_BASE_URL = http://allocation.openwcs.local:8091   # on order-management, etc.
    
  4. Register each as an ECS Service (desired count ≥1) joined to the Service Connect namespace. Only the gateway service sits behind the ALB; everything else stays private.

  5. Point CloudFront/Route 53 at the ALB; deploy the UI bundle to S3.

Tip: start the gateway + the services it fronts, prove /api/.../actuator/health through the ALB, then scale out the adapters and integration services. Keep Keycloak as its own Fargate service (or use an existing IdP) and enable OPENWCS_SECURITY_ENABLED once tokens flow.


5 · Google Cloud (GKE / Cloud Run)

Recommended: GKE — because openWCS needs Kafka and a couple of always-on Kafka consumers (inventory, the txlog relay), a Kubernetes cluster is the cleanest fit.

Need GCP service
Images Artifact Registry (<region>-docker.pkg.dev/<project>/openwcs)
PostgreSQL Cloud SQL for PostgreSQL 16, database openwcs
Kafka self-managed on GKE (e.g. Strimzi/Bitnami) or Confluent Cloud
Cluster GKE Autopilot
Public entry GKE Ingress / Gateway API → the gateway Service
UI Cloud Storage static site + Cloud CDN

Each service becomes a Deployment + Service; the k8s Service name is the hostname, so OPENWCS_URI_* / *_BASE_URL map straight onto http://inventory:8082, etc. — i.e. the Compose env translates almost 1:1 into manifests. Put the DB password and Kafka creds in a Secret, expose only the gateway through Ingress, and run Cloud SQL Auth Proxy (or private IP) for the DB.

Cloud Run also works for the stateless HTTP services (gateway + most domain services) with Cloud SQL and Confluent Cloud for Kafka — use internal ingress + service-to-service IAM, and set min-instances=1 on the Kafka-consuming services so projections keep up. If you'd rather not manage that nuance, stay on GKE for the whole stack.

gcloud artifacts repositories create openwcs --repository-format=docker --location=<region>
gcloud auth configure-docker <region>-docker.pkg.dev
# …push images from §1 with REG=<region>-docker.pkg.dev/<project>/openwcs, then apply your manifests

6 · Microsoft Azure (Container Apps / AKS)

Recommended: Azure Container Apps (ACA) — managed, scale-to-zero-capable containers with a built-in internal DNS, which suits the service mesh well.

Need Azure service
Images Azure Container Registry (ACR)
PostgreSQL Azure Database for PostgreSQL — Flexible Server, database openwcs
Kafka Azure Event Hubs (Kafka-compatible endpoint) or Kafka on AKS
Runtime Azure Container Apps environment (one app per service)
Public entry external ingress on the gateway app; all others internal ingress
UI Azure Static Web Apps or Blob Storage + CDN

Within an ACA environment, apps reach each other at https://<app-name>.internal.<env-domain> (or simply the app name) — so the service URLs become those internal FQDNs. Event Hubs speaks the Kafka protocol, so SPRING_KAFKA_BOOTSTRAP_SERVERS=<namespace>.servicebus.windows.net:9093 with SASL config works for the producers/consumers. Store the DB and Event Hubs connection strings in the environment's secrets.

az acr create -n <acr> -g <rg> --sku Standard && az acr login -n <acr>
# push images from §1 with REG=<acr>.azurecr.io
az containerapp env create -n openwcs -g <rg> -l <region>
az containerapp create -n gateway -g <rg> --environment openwcs \
   --image <acr>.azurecr.io/openwcs-gateway:<tag> --target-port 8080 --ingress external
# …repeat per service with --ingress internal and the SPRING_*/OPENWCS_* env vars

AKS is the alternative for full Kubernetes control; the manifest model from §5 applies unchanged (swap Cloud SQL→Flexible Server, Artifact Registry→ACR).


7 · Kubernetes — starter manifests (horizontal scaling)

Ready-to-apply plain-YAML manifests live in deploy/k8s/ — a starting point for running the whole estate on Kubernetes. Edit config.yaml (image registry, DB/Kafka endpoints, DB Secret) before applying. Full conventions and caveats are in deploy/k8s/README.md and Horizontal Scaling.

deploy/k8s/
├── namespace.yaml   # the openwcs namespace
├── config.yaml      # shared ConfigMap + DB Secret placeholder
├── services.yaml    # Deployment + Service for every Java service
├── adapters.yaml    # Deployment + Service for the Go adapters (sniffer pinned to replicas: 1)
└── hpa.yaml         # HPA 2→10 on CPU for the high-traffic services
kubectl apply -f deploy/k8s/

What needed fixing to scale safely — two areas that assumed a single instance were fixed for this release:

Area Problem Fix
Outbox relays & off-peak jobs @Scheduled on every replica → double-publish/duplicate tasks ShedLock @SchedulerLock; one replica runs per tick
Conveyor loop capacity check count-then-enter race between replicas → capacity exceeded pessimistic row lock on the loop row

Two operational constraints to know before scaling:

  1. Kafka partitions cap consumer replicas. The inventory stock projection and slotting velocity learner scale only up to the partition count of txlog.stream — size that topic to your intended max replica count.
  2. The conveyor-sniffer is pinned to one replica. It holds a long-lived TCP stream per controller; round-robin load-balancing fragments telegrams. Scale sniffing by running one instance per controller (separate Deployments with distinct SNIFFER_LISTEN/allowlists) or use a session-affinity (sticky) L4 load balancer.

See Horizontal Scaling for the full guarantee matrix and ShedLock details.


Production checklist

Regardless of target:

  • One PostgreSQL, many schemas. All services share a single managed database (openwcs) with a schema per service. Size it for your busiest service, enable automated backups + PITR.
  • Managed Kafka. Use MSK / Event Hubs / Confluent rather than self-managing brokers. The transaction log → Kafka stream is the backbone; size partitions and retention deliberately.
  • Expose only the gateway. Keep services, DB, Kafka, and Keycloak on the private network. Terminate TLS at the edge (LB / ingress / reverse proxy).
  • Turn security on. Set OPENWCS_SECURITY_ENABLED=true, run Keycloak (or your IdP) with the openwcs realm, rotate every default password/secret, and store secrets in the platform's secret manager — never in images. See Security.
  • Scaling. All services scale horizontally — ShedLock prevents duplicate scheduled-job execution across replicas; the conveyor-loop capacity check uses a pessimistic lock. Starter Kubernetes manifests and HPAs live in deploy/k8s/ (see §7 and Horizontal Scaling). Mind the Kafka-consuming services (inventory, txlog relay) — their throughput is bound by topic partitions, and they should not scale to zero if you need live projections.
  • Adapters live near the equipment. Device adapters (and especially the conveyor-sniffer, which needs L2 access to the conveyor network and a configured WAREHOUSE_ID / ALLOWED_IPS) usually run at the edge/on-prem, not in the cloud. They reach the WCS over HTTPS.
  • Observability & backups. Ship logs/metrics (/actuator endpoints are exposed), alert on consumer lag and gateway 5xx, and rehearse a database restore before go-live.

Public website (Node.js) — contact form

The public/ directory is the openWCS marketing site — a small Express/EJS app (Node 18+) deployed separately on Hostinger, Render, or similar. Most of it is self-contained, but the "Contact us" form requires a Microsoft Graph mailbox to send mail.

How it works

Clicking Contact us opens a modal; on submit the browser posts to POST /api/contact. The handler validates input, applies a honeypot field (company) and a per-IP rate limit (5 requests / 10 minutes), then calls services/graph.js which:

  1. Fetches an OAuth2 client-credentials token from Azure AD.
  2. Calls POST /users/{mailbox}/sendMail via the Graph API, with the submitter's address as reply-to (so hitting Reply in your mail client reaches them, not the platform mailbox).
  3. Returns the standard JSON shape: { ok: true } on success, or { ok: false, error } for 400 / 429 / 503 / 500 responses. Graph errors are logged server-side and never surfaced to the browser. On success the UI shows a confirmation message, then automatically closes the modal after ~2 seconds.

Until all four required env vars are set, every submission returns 503 — not configured. The static GitHub Pages mirror has no server, so the form is inert there.

Required environment variables

Variable Purpose
MS_GRAPH_TENANT_ID Azure AD tenant id
MS_GRAPH_CLIENT_ID App registration (client) id
MS_GRAPH_CLIENT_SECRET App registration client secret — keep secret
MS_GRAPH_MAIL_ADDRESS Sender mailbox; the app registration must have Mail.Send permission for it
MS_GRAPH_FROM_NAME (optional) Display name on the from address (default: openWCS)
CONTACT_TO (optional) Recipient for contact submissions (default: contact@brettljausn.ai)

See public/.env.example for the full annotated template. The host (Hostinger / Render / etc.) injects these as process environment variables — server.js does not load .env files.

Azure AD setup (one-time)

  1. Register an app in Azure AD (App registrations → New registration).
  2. Under API permissions, add Microsoft Graph → Application permissions → Mail.Send. Grant admin consent.
  3. Under Certificates & secrets, create a client secret and note the value.
  4. Note the Application (client) ID and Directory (tenant) ID from the Overview page.
  5. Set the four required env vars on your hosting platform.

See also

Clone this wiki locally