Skip to content

Appliance Image

Rod Christiansen edited this page Oct 8, 2026 · 2 revisions

Appliance Image

packer/ bakes the Compose stack into a VM image so a ReportMate host can be deployed as an appliance rather than assembled by hand. The output is the same three containers; the image just ships Docker, the project files, and a systemd unit that starts them.

What the template builds

packer/reportmate.pkr.hcl defines one qemu source producing a qcow2 disk.

Setting Value
Base image https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img (iso_url variable)
Checksum none
Disk 20G, qcow2
Accelerator kvm
SSH user / password ubuntu / ubuntu
Output output-reportmate/reportmate-appliance.qcow2
Mode headless

Two variables are exposed: reportmate_tag (default latest), passed to the provisioning script as REPORTMATE_TAG, and iso_url. The SSH timeout is 20m, and the build shuts the VM down with sudo shutdown -P now.

The build copies the whole repo to /tmp/reportmate, then runs packer/setup.sh.

Building

Requires Packer and a working KVM host.

packer init packer/reportmate.pkr.hcl
packer build packer/reportmate.pkr.hcl

reportmate_tag reaches setup.sh as the REPORTMATE_TAG environment variable, but neither setup.sh nor first-boot.sh writes it into .env, so the appliance runs latest unless the .env or docker-compose.yml you ship pins the image tags. See Upgrading for the tags each image carries.

What setup.sh does

  1. Installs ca-certificates, curl, and gnupg.
  2. Adds Docker's apt keyring and repository for the running Ubuntu release.
  3. Installs docker-ce, docker-ce-cli, containerd.io, and docker-compose-plugin.
  4. Copies the staged repo from /tmp/reportmate to /opt/reportmate and deletes /opt/reportmate/packer.
  5. Writes /opt/reportmate/first-boot.sh and makes it executable.
  6. Writes /etc/systemd/system/reportmate.service and enables it.

The script runs with set -euo pipefail, so any failed step fails the build.

First boot

reportmate.service is a Type=simple unit that requires and starts after docker.service and network-online.target, works from /opt/reportmate, runs first-boot.sh, and restarts on failure.

first-boot.sh is idempotent by way of one check: if /opt/reportmate/.env does not exist, it copies .env.example and replaces the placeholder strings with openssl rand -hex 24 values — one value for the DB password, and one value substituted globally for every occurrence of change-me-to-a-long-random-string. It then execs docker compose up.

Two consequences worth knowing:

  • The three application secrets end up identical. The sed -i ... /g replaces API_INTERNAL_SECRET, REPORTMATE_PASSPHRASE, and NEXTAUTH_SECRET with the same generated string. That is functional but means one leaked value is three leaked values. Supply your own .env if you care.
  • Supplying your own .env wins. Drop a complete .env into /opt/reportmate/ before first boot (cloud-init, a mounted config drive, or a snapshot) and the generation step is skipped entirely.

To read the secrets that were generated, cat /opt/reportmate/.env on the booted appliance.

Before you ship an appliance

Two things about the build are fine for a local qcow2 and wrong for anything you hand to someone else.

The qemu source authenticates to the build VM with the cloud image's ubuntu / ubuntu credentials. Nothing in setup.sh changes or disables that account, so whatever the base image does with those credentials is what your appliance does. Harden the account, or inject an SSH key, before distributing an image.

The file provisioner copies ${path.root}/.. — the entire working tree — into the image. .env is gitignored, but if one exists in your checkout it is copied to /opt/reportmate/.env and becomes part of the image, secrets and all. Build from a clean clone.

Other builders

The template ships only the qemu source. The README notes that an amazon-ebs or azure-arm source can be added to the same template and reuse packer/setup.sh unchanged. That is a supported shape, not something the repo currently contains — you write the source block.

CI

.github/workflows/ci.yml validates the appliance definition on push, pull request, and manual dispatch. It copies .env.example to .env and runs docker compose config --quiet, runs packer fmt -check -diff (report-only, does not fail the build), packer init plus packer validate, and shellcheck --severity=error packer/setup.sh.

CI does not build the qcow2, does not start the stack, and does not build or publish any container image — this repo consumes the GHCR images rather than producing them.

Clone this wiki locally