Skip to main content

Overview

The Single VM deployment packages the entire Popsink data-plane into a single virtual machine image. It is designed for:
  • Proof-of-concept and evaluation — get Popsink running in your environment quickly, without managing a distributed infrastructure.
  • Lightweight workloads — small teams or low-traffic environments where horizontal scalability is not a requirement.
This deployment model is intentionally simple. For high-availability or large-scale production use, see the Kubernetes deployment instead.
The VM is distributed as a pre-built image (.qcow2 for KVM/QEMU, .ova for VMware vSphere). No Kubernetes knowledge is required to operate it.

VM Platform

PropertyValue
Operating systemUbuntu 24.04 LTS “Noble Numbat”
Architecturex86-64 (amd64)
Base imageUbuntu Server Cloud Image (noble-server-cloudimg-amd64.img)
Container runtimek3s v1.35.5+k3s1 (single-node Kubernetes)
Disk formatqcow2 (KVM/QEMU) / OVA (VMware vSphere)
Disk size20 GiB (sparse — actual usage is much lower at rest)
The VM does not require internet access after first boot, but it does need to reach the external dependencies listed below.

Prerequisites

Hardware

ParameterMinimum
CPU4 vCPU
RAM16 GB
Disk~25 GB
Hypervisors that pre-allocate the full sparse image size require significantly more datastore space. Confirm the allocation behaviour of your target hypervisor before import.

External Dependencies

The VM requires two external services to be reachable before it can start. These are not bundled in the image.
ServiceRequirement
PostgreSQL 17A database instance accessible from the VM. The VM will not start if unreachable.
S3-compatible object storeA bucket accessible from the VM (AWS S3, MinIO, or equivalent). The VM will not start if unreachable.
Ensure outbound connectivity from the VM to both services before deployment.

Supported Hypervisors

FormatTarget
popsink-deploy-vm-<version>.qcow2KVM/QEMU
popsink-deploy-vm-<version>.ovaVMware vSphere

Getting the VM Image

Popsink provides pre-built images for each release. Contact your Popsink representative to receive a download link and its accompanying .sha256 checksum file. Once you have both files, verify the image integrity before importing:
sha256sum -c popsink-deploy-vm-<version>.<format>.sha256
Expected output: popsink-deploy-vm-<version>.<format>: OK

Network Configuration

Inbound

PortProtocolPurpose
80TCPPopsink data-plane — all HTTP traffic
Open port 80 from your internal network to the VM. TLS termination and HTTPS are your responsibility — the VM does not perform TLS.

Outbound

DestinationRequiredEffect if unreachable
Your PostgreSQL 17 instanceHardVM fails to start
Your S3-compatible object storeHardVM fails to start
Popsink authentication backend (HTTPS)SoftPipelines run normally; UI logins fail
Ensure the hard requirements are in place before booting the VM. The soft dependency (authentication backend) only affects UI logins — pipelines are unaffected.

Outbound Proxy

If your network enforces an outbound proxy, set HTTP_PROXY, HTTPS_PROXY, and NO_PROXY in the configuration file (see Configuration).

Deployment

KVM/QEMU

  1. Build the seed.iso configuration image first — see Cloud-Init below for step-by-step instructions.
  2. Import the image and attach the seed.iso as a virtual CD-ROM in a single command. The example below uses virt-install; virsh and virt-manager are equally valid:
    virt-install \
      --name popsink \
      --memory 16384 \
      --vcpus 4 \
      --disk path=popsink-deploy-vm-<version>.qcow2,format=qcow2 \
      --disk path=seed.iso,device=cdrom,readonly=yes \
      --import \
      --os-variant generic \
      --noautoconsole
    
  3. Boot the VM. Cloud-init reads the ISO on first boot and configures the data-plane automatically.

VMware vSphere

  1. Build the seed.iso configuration image first — see Cloud-Init below for step-by-step instructions.
  2. In the vSphere UI, right-click the target datacenter or cluster → Deploy OVF Template.
  3. Follow the wizard:
    • At the Select networks step, map the VM’s network adapter to a port group that satisfies the inbound and outbound requirements listed above.
    • Confirm the hardware summary: 4 vCPU, 16 GB RAM, 20 GiB disk.
    • At the Select storage step, choose thin provisioning unless thick is required by your policy.
  4. After the deploy task completes, do not boot the VM yet. Upload seed.iso to your vSphere datastore:
    • In the vSphere Client, navigate to the target datastore → Files tab → Upload.
    • Upload the seed.iso file you generated.
  5. Attach seed.iso as a CD-ROM drive on the VM:
    • Right-click the VM → Edit Settings.
    • Click Add Other DeviceCD/DVD Drive.
    • Select Datastore ISO File and browse to the uploaded seed.iso.
    • Ensure Connect at Power On is checked, then click OK.
  6. Boot the VM. Cloud-init reads the ISO on first boot and configures the data-plane automatically.

Configuration

The VM reads its configuration from /etc/popsink/config.env at first boot, then runs popsink-configure.service which applies the settings to the data-plane.

Cloud-Init

Cloud-init is the standard Linux mechanism for configuring a VM on first boot. On both KVM/QEMU and VMware, configuration is delivered via a small ISO image — the seed.iso — which you attach as a virtual CD-ROM drive. The VM reads it automatically at boot; no manual console access is needed. The seed.iso contains two plain-text files:
  • meta-data — minimal instance identity (hostname and ID).
  • user-data — the actual configuration in #cloud-config format.

Step 1 — Create a working directory

mkdir popsink-cloudinit
cd popsink-cloudinit

Step 2 — Create meta-data

instance-id: popsink-vm
local-hostname: popsink-vm
Save this as a file named exactly meta-data (no extension).

Step 3 — Create user-data

Save the following as user-data, filling in all REPLACE_WITH_* placeholders with your values:
#cloud-config
write_files:
  - path: /etc/popsink/config.env
    owner: root:root
    permissions: '0600'
    content: |
      PG_HOST=db.example.com
      PG_PORT=5432
      PG_DATABASE=popsink
      PG_USER=popsink
      PG_PASSWORD=REPLACE_WITH_SECRET

      KORA_DB_HOST=db.example.com
      KORA_DB_PORT=5432
      KORA_DB_DATABASE=kora
      KORA_DB_USER=kora
      KORA_DB_PASSWORD=REPLACE_WITH_SECRET

      S3_BUCKET=s3://my-bucket/kafka/
      S3_REGION=eu-west-1
      S3_ACCESS_KEY_ID=REPLACE_WITH_KEY_ID
      S3_SECRET_ACCESS_KEY=REPLACE_WITH_SECRET

      DEPLOYMENT_ID=REPLACE_WITH_UUID
      DEPLOYMENT_JWT_TOKEN=REPLACE_WITH_SECRET
      INGRESS_URL=http://vm.example.com

      ADMIN_PANEL_USERNAME=admin
      ADMIN_PANEL_PASSWORD=REPLACE_WITH_SECRET

      JWT_SECRET=REPLACE_WITH_SECRET
      CONNECTOR_CONFIG_ENCRYPTION_KEY=REPLACE_WITH_SECRET
See Parameter Reference for the description and constraints of each value.
Security: This file contains secrets. Do not commit it to version control with real credentials. Keep it in a secure location and delete it once the VM is configured.
SSH access (optional): No SSH keys are pre-provisioned in the image. To enable SSH, add an ssh_authorized_keys stanza to your user-data alongside the write_files block:
ssh_authorized_keys:
  - ssh-rsa AAAA...your-public-key...
Without this, the VM is only accessible via the hypervisor console.

Static IP (optional)

By default the VM uses DHCP. If your environment requires a fixed IP address, create a third file named network-config in the same directory as meta-data and user-data:
version: 2
ethernets:
  eth0:
    addresses:
      - 192.168.1.50/24
    gateway4: 192.168.1.1
    nameservers:
      addresses: [192.168.1.1]
Replace eth0 with the actual interface name for your VM (visible in the hypervisor console at first boot), and adjust the IP addresses for your network. If you create this file, include it when generating the ISO in Step 4 below.

Step 4 — Generate seed.iso

Choose the tool available on your machine. All three produce an equivalent result. Option A — cloud-localds (Linux, simplest) cloud-localds is purpose-built for this task and requires no flags:
# Install on Ubuntu/Debian
sudo apt-get install cloud-image-utils

# Without static IP
cloud-localds seed.iso user-data meta-data

# With static IP (if you created a network-config file)
cloud-localds seed.iso user-data meta-data --network-config network-config
Option B — genisoimage or mkisofs (Linux) genisoimage and mkisofs are interchangeable — use whichever is installed:
# Install on Ubuntu/Debian
sudo apt-get install genisoimage

# Without static IP
genisoimage -output seed.iso -volid cidata -joliet -rock user-data meta-data

# With static IP (if you created a network-config file)
genisoimage -output seed.iso -volid cidata -joliet -rock user-data meta-data network-config
# Install on RHEL/Fedora
sudo dnf install genisoimage

# Without static IP
mkisofs -output seed.iso -volid cidata -joliet -rock user-data meta-data

# With static IP
mkisofs -output seed.iso -volid cidata -joliet -rock user-data meta-data network-config
The -volid cidata flag is mandatory. Cloud-init identifies the NoCloud datasource by looking for a volume labelled cidata — any other label will be ignored.
Option C — hdiutil (macOS) hdiutil is built into macOS. It takes a directory rather than individual files, so run it from the parent directory. If you created a network-config file, simply place it in the directory alongside the other files — no additional flags are needed:
hdiutil makehybrid -o seed.iso -hfs -joliet -iso -default-volume-name cidata popsink-cloudinit/

Step 5 — Attach as CD-ROM and boot

Cloud-init reads the ISO exactly once on first boot. The ISO is no longer needed afterwards and can be detached and deleted.

Manual Configuration

Use this method only if your hypervisor cannot attach a CD-ROM drive, or if the VM has already booted without one. Deliver the configuration directly at the hypervisor console:
  1. Create the configuration file:
    sudo install -m 0600 -o root -g root /dev/null /etc/popsink/config.env
    sudo tee /etc/popsink/config.env <<'EOF'
    PG_HOST=db.example.com
    # ... (fill in all required parameters — see table below)
    EOF
    
  2. Start the configure service:
    sudo systemctl start popsink-configure.service
    
  3. Verify success:
    sudo journalctl -u popsink-configure.service
    
    A successful run ends with:
    Popsink cloud-init configuration applied successfully. Data-plane is running.
    

Re-applying Configuration

After a successful first run, the configure service creates a marker file /etc/popsink/.configured and will not re-run on subsequent boots. If you need to update the configuration — for example, to correct a credential or change the PostgreSQL host — update the file and clear the marker:
  1. Edit /etc/popsink/config.env with the new values.
  2. Remove the marker and restart the service:
    sudo rm /etc/popsink/.configured
    sudo systemctl restart popsink-configure.service
    
  3. Verify success:
    sudo journalctl -u popsink-configure.service
    

Parameter Reference

PostgreSQL (required)

ParameterRequiredDefaultDescription
PG_HOSTYesPostgreSQL host (FQDN or IP)
PG_PORTNo5432PostgreSQL port
PG_DATABASENopopsinkDatabase name
PG_USERNopopsinkDatabase username
PG_PASSWORDYesDatabase password

Kora Schema Registry Database (required)

Kora is Popsink’s internal schema registry — it tracks the structure of the data flowing through your pipelines. It requires its own PostgreSQL database, but can share the same PostgreSQL instance as the main data-plane. Point KORA_DB_HOST at the same host and use a separate database name (e.g. kora); sharing the same database works but is not the default.
ParameterRequiredDefaultDescription
KORA_DB_HOSTYesKora database host (FQDN or IP)
KORA_DB_PORTNo5432Port
KORA_DB_DATABASENokoraDatabase name
KORA_DB_USERNokoraUsername
KORA_DB_PASSWORDYesPassword

S3-Compatible Storage (required)

ParameterRequiredDefaultDescription
S3_BUCKETYesFull S3 URL including prefix: s3://bucket/prefix/not just the bucket name
S3_REGIONYesAWS region (e.g. eu-west-1). Use any non-empty value for S3-compatible services that don’t require a real region.
S3_ACCESS_KEY_IDYesAccess key ID
S3_SECRET_ACCESS_KEYYesSecret access key
S3_ENDPOINTNoCustom endpoint URL (e.g. MinIO). Omit for AWS S3.

Popsink Platform (required)

ParameterRequiredDescription
DEPLOYMENT_IDYesUUID for this deployment — provided by Popsink
DEPLOYMENT_JWT_TOKENYesDeployment JWT token — provided by Popsink
INGRESS_URLYesDisplay-only URL of this VM (e.g. http://10.0.1.50). Does not configure routing or TLS.

Admin Credentials (required)

ParameterRequiredDescription
ADMIN_PANEL_USERNAMEYesAdmin UI login username
ADMIN_PANEL_PASSWORDYesAdmin UI login password

Security Keys (required)

ParameterRequiredDescription
JWT_SECRETYesJWT signing secret. Changing this invalidates all active sessions.
CONNECTOR_CONFIG_ENCRYPTION_KEYYesEncryption key for connector configurations. Back this up before going live. If lost, all connector configurations become permanently unreadable — Popsink cannot recover this key.

Outbound Proxy (optional)

ParameterDescription
HTTP_PROXYHTTP proxy URL (e.g. http://proxy.example.com:3128)
HTTPS_PROXYHTTPS proxy URL
NO_PROXYComma-separated list of hosts to bypass the proxy

Health Verification

After the VM boots, verify each layer in order. Fix a failing layer before investigating the ones above it.

Layer 1 — Data-Plane (network reachability)

From any host with network access to the VM:
curl http://<vm-ip>/api/readyz
Expected: HTTP 200.

Layer 2 — k3s Runtime

At the VM console or over SSH:
systemctl status k3s
Expected: Active: active (running). k3s may take a minute to reach this state on first boot.

Layer 3 — Configuration Service

journalctl -u popsink-configure.service
Look for the success message at the end of the output. If the service failed, the log identifies the cause (PostgreSQL unreachable, invalid S3 credentials, etc.).

Layer 4 — Boot Check

journalctl -u popsink-boot-check.service
  • No output: configuration was present at boot — this layer is healthy.
  • Output present: no configuration was found when the VM booted. Deliver the configuration file and re-run the configure service:
    sudo rm -f /etc/popsink/.configured
    sudo systemctl start popsink-configure.service
    

Troubleshooting

SymptomLikely causeFix
curl /api/readyz — no response or connection refusedConfiguration service failed, or k3s still startingCheck Layer 3 then Layer 2 in Health Verification above
popsink-configure.service shows a PostgreSQL errorPG_HOST unreachable, wrong port, or bad credentialsTest connectivity from the VM console: nc -zv <PG_HOST> <PG_PORT>; verify pg_hba.conf allows the VM’s IP
popsink-configure.service shows an S3 errorWrong S3_BUCKET format or unreachable endpointS3_BUCKET must be a full URL — s3://bucket/prefix/ not just bucket
VM booted but appears unconfiguredseed.iso not attached, or wrong volume labelCheck journalctl -u popsink-boot-check.service; verify the ISO was generated with -volid cidata; re-deliver and re-run (see Re-applying Configuration)
UI loads but all logins failPopsink authentication backend unreachableVerify outbound HTTPS from the VM to the Popsink auth backend; check proxy settings if applicable
Port 80 unreachable from another host, but the VM is healthyNetwork firewall blocking inbound port 80Open TCP port 80 from your internal network to the VM’s IP

Accessing Popsink

Once all health checks pass, open a browser and navigate to:
http://<vm-ip>/
Log in with the ADMIN_PANEL_USERNAME and ADMIN_PANEL_PASSWORD you set in the configuration.

Updates

The VM is stateless — all data lives in your external PostgreSQL instance and S3 bucket. Updating Popsink means replacing the VM with a newer image; no in-place upgrades are required. Replacement procedure:
  1. Boot the new VM with the same configuration.
  2. Confirm curl http://<new-vm-ip>/api/readyz returns HTTP 200.
  3. Run a test pipeline end-to-end on the new VM.
  4. Pause any active pipelines on the old VM, then switch network routing to the new VM.
  5. Resume pipelines and decommission the old VM.
The old and new VM can run against the same database and S3 bucket simultaneously — state is in the external systems, and concurrent access is safe. Do not serve production traffic from both VMs at the same time, as pipelines may execute in duplicate.
If the new VM fails verification, restore routing to the old VM. Its state is unaffected.

Limitations

LimitationDetails
No horizontal scalingPerformance is bounded by a single machine.
Single point of failureIf the VM goes down, the data-plane is unavailable.
No built-in TLSHTTPS termination is your responsibility.
For production-grade scalability and high availability, consider the Kubernetes deployment.