Set up on VMware
This guide runs Lumaft as a container on one vSphere virtual machine. It covers the two things that differ most from a public cloud: where the database lives and how traffic gets in and out.
It applies equally to air-gapped sites running Enterprise Advanced Security; those differences are called out where they occur.
Architecture
Corporate network ──443──▶ ┌──────────────────────────────────────────┐
(or existing LB) │ vSphere VM (Ubuntu LTS / RHEL) │
│ ┌──────────┐ ┌────────────────────┐ │
│ │ reverse │──▶ │ lumaft │ │──▶ S3 / S3-compatible
│ │ proxy │ │ 127.0.0.1:8080 │ │──▶ private registry
│ │ :443 │ │ /data ← data VMDK │ │──▶ licensing (unless offline)
│ └──────────┘ └────────────────────┘ │
└──────────────────────────────────────────┘
Before you begin
- A vSphere VM template with a supported Linux distribution and a container runtime (Docker Engine or Podman). Two vCPU and 4 GiB of memory leave room for the proxy beside Lumaft's 1 vCPU / 2 GiB baseline.
- A DNS name for Lumaft and a certificate your browsers trust — from your internal CA or a public one.
- Network reachability from the VM to the object store that holds your state.
- The verified image digest, mirrored into a registry the VM can reach if the VM has no internet access. See Deployment and setup.
- Your backend details. See Find your backend details.
Persistent storage
SQLite runs in WAL mode and needs an exclusive, durable, block-backed volume presented to the guest as local storage. On vSphere that means a dedicated virtual disk, not a network share inside the guest.
| Storage shape | Supported for /data |
|---|---|
| A second VMDK on a VMFS, vSAN, or NFS datastore | Yes |
| An RDM (raw device mapping) | Yes |
| An NFS or SMB share mounted inside the guest | No |
| A disk shared between two VMs (multi-writer) | No |
| The VM's root disk | Works, but keep data separate |
The datastore backing the VMDK may itself be NFS; what matters is that the guest sees a block device. WAL's locking requirements apply to the guest filesystem, and a VMDK gives the guest ext4 or XFS on a local block device.
Step 1: Add the data disk
- Edit the VM settings and add a new hard disk. Size it for your retention window; 50 GiB is a comfortable starting point.
- Place it on a datastore with the durability and snapshot policy you want for the database.
- Set the disk to Independent – Persistent if you take VM snapshots for the OS and do not want the database included in them. Database backups are taken cold, not by snapshot.
- In the guest, format and mount it:
sudo mkfs.xfs -L lumaft-data /dev/sdb
sudo mkdir -p /srv/lumaft/data
echo 'LABEL=lumaft-data /srv/lumaft/data xfs defaults,nofail 0 2' | sudo tee -a /etc/fstab
sudo mount -a
sudo chown 1000:1000 /srv/lumaft/data
sudo chmod 700 /srv/lumaft/data
Never attach the same VMDK to a second VM. Lumaft holds an ownership lock on
lumaft.db.owner; a second replica pointed at the same database refuses to start with
ownership-conflict, and a multi-writer disk would bypass the protection the lock provides.
PostgreSQL instead of SQLite
If you already operate PostgreSQL on-premises and hold a Business license, Lumaft can use it on one replica; Enterprise adds coordinated replicas. The VM then needs no data disk. See Database integration.
Networking
Inbound
Lumaft binds to the loopback interface. Terminate TLS in one of two places:
- On the VM. Caddy or nginx on
:443forwarding to127.0.0.1:8080. Simplest when the VM is directly addressable. - On an existing load balancer (NSX Advanced Load Balancer, F5, HAProxy). Publish the
container on the VM's private address (
--publish 10.10.20.5:8080:8080) and restrict the guest firewall so8080admits only the load balancer. The load balancer holds the certificate and forwards HTTP to8080. Health check:GET /api/v1/readiness, expect200.
Either way, browsers connect only over HTTPS. Leave LUMAFT_ALLOW_HTTP unset.
Guest firewall (for example ufw or firewalld):
| Direction | Port | Source | Purpose |
|---|---|---|---|
| Inbound | 443 | Client ranges or the LB | Console and runner traffic |
| Inbound | 8080 | Load balancer only (if used) | Forwarded traffic |
| Inbound | 22 | Management network only | Administration |
Outbound
Lumaft makes three kinds of outbound HTTPS calls:
| Destination | When | Air-gapped equivalent |
|---|---|---|
| The object store | Every observation cycle | An on-premises S3-compatible store |
| The container registry | At pull time | A private registry mirror |
| The licensing service | Connected Business or Enterprise renewals | An offline-site license file (Enterprise Advanced Security) |
Allow those destinations at your egress firewall. Lumaft reads the object store directly over
HTTPS; if the store uses a certificate from your internal CA, mount the CA bundle and set
NODE_EXTRA_CA_CERTS:
--mount type=bind,source=/srv/lumaft/run/ca-bundle.pem,target=/run/lumaft/ca-bundle.pem,readonly \
--env NODE_EXTRA_CA_CERTS=/run/lumaft/ca-bundle.pem
Do not disable certificate verification.
Backend credentials
An on-premises VM has no cloud workload identity. Options, in order of preference:
- S3-compatible store with its own access keys. Create a read-only key pair scoped to the
state namespace and pass it as
AWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEYthrough an--env-filewith mode0600. - Static IAM user keys, scoped to the read-only policy in Connect a backend, rotated on a schedule.
Step 2: Secret files
sudo mkdir -p /srv/lumaft/run && sudo chmod 700 /srv/lumaft/run
sudo sh -c 'umask 077; printf "%s\n" "$(openssl rand -base64 24)" > /srv/lumaft/run/admin-password'
sudo sh -c 'umask 077; cat > /srv/lumaft/run/backends.json' <<'JSON'
{
"schemaVersion": 1,
"backends": [
{
"kind": "s3",
"id": "private-cloud",
"displayName": "Private cloud infrastructure",
"bucket": "pulumi-state",
"layout": "project-scoped",
"region": "local",
"endpoint": "https://s3.internal.example",
"forcePathStyle": true,
"enabled": true
}
]
}
JSON
sudo chown -R 1000:1000 /srv/lumaft/run
sudo chmod 600 /srv/lumaft/run/*
endpoint must be an HTTPS origin with no path, query, or credentials. forcePathStyle: true
is typical for S3-compatible stores that do not serve virtual-hosted bucket names.
Step 3: Run Lumaft
sudo docker run --name lumaft \
--detach \
--restart unless-stopped \
--init \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=64m \
--publish 127.0.0.1:8080:8080 \
--mount type=bind,source=/srv/lumaft/data,target=/data \
--mount type=bind,source=/srv/lumaft/run,target=/run/lumaft,readonly \
--env-file /srv/lumaft/s3-credentials.env \
--env LUMAFT_LOCAL_ADMIN_PASSWORD_FILE=/run/lumaft/admin-password \
--env LUMAFT_BACKENDS_FILE=/run/lumaft/backends.json \
--env LUMAFT_PUBLIC_ORIGIN=https://lumaft.internal.example \
"registry.internal.example/dekglas/lumaft@sha256:<digest>"
curl -fsS http://127.0.0.1:8080/api/v1/readiness
Put your reverse proxy in front (the EC2 guide's containerized Caddy works unchanged here, or use your existing load balancer), sign in, and confirm the backend diagnostic under Administration → Backends. Then remove the password environment variable and securely delete the password file. Docker Compose and systemd turns this command into a unit that starts on boot.
Air-gapped sites
Enterprise Advanced Security runs the same image with an organization-wide offline license and no outbound licensing calls.
On a connected machine, verify the release signature, pull the image by digest, and transfer it with
docker save/docker loador by mirroring to the private registry. The digest is the artifact identity; carry it with the image.Obtain the signed
offline-sitelicense document from Dekglas and mount it as a protected file, mode0600, owned by UID 1000:--mount type=bind,source=/srv/lumaft/run/site-license,target=/run/lumaft/site-license,readonly \ --env LUMAFT_OFFLINE_SITE_LICENSE_FILE=/run/lumaft/site-licenseOpen Administration → Licensing to confirm the edition, grants, and expiry the license resolved to.
The license has its own signed expiry and grace period. Renewal is a file replacement and a restart.
Operate on vSphere
Backups
Backups are cold: stop the container, copy lumaft.db, start the container. Copy the file to
backup storage that is independent of the datastore.
sudo docker stop -t 30 lumaft
sudo cp /srv/lumaft/data/lumaft.db /mnt/backups/lumaft-$(date -u +%Y%m%dT%H%M%SZ).db
sudo docker start lumaft
A VM snapshot or a datastore-level backup of a running VM is crash-consistent. It is a useful safety net for the operating system; it is not a verified database backup.
High availability
Run one VM. vSphere HA restarts it on another host after a host failure, and the data VMDK moves with it. Do not run two Lumaft VMs against one database; use PostgreSQL with an Enterprise license when you need coordinated replicas.
Upgrades
- Stop the container cleanly and take a cold backup.
- Pull or load the new digest and run the same command against
/srv/lumaft/data. - Confirm readiness. A refused start naming a gated migration has migrated nothing; see Database integration.
Monitoring
Poll /api/v1/readiness from your monitoring system and the authenticated
/api/v1/storage-health endpoint for disk pressure. Alert when dataDirectory.freeBytes falls
below the growth headroom you sized the disk for.