Deployment

Three paths, one source of truth (the flake). Match the path to the host:

flowchart TD
    start["I want OpenSASE running"] --> q{"Nix on this machine?"}
    q -- "no / macOS / Windows" --> ghcr["Pull GHCR images<br/>docker compose up"]
    q -- "yes" --> nix{"What kind of target?"}
    nix -- "try it out" --> vm["nix build .#vm-x86_64<br/>run-opensase-vm"]
    nix -- "real machine I control" --> rebuild["nixos-rebuild switch<br/>--flake .#opensase"]
    nix -- "remote machines" --> target["nixos-rebuild switch<br/>--target-host root@ip"]
    nix -- "want containers anyway" --> local["nix run .#load-images<br/>docker compose up"]

Path 1 — Docker compose (no Nix, any OS)

git clone https://github.com/shsingh/opensase && cd opensase
docker compose -p opensase up -d

Works on Linux, macOS (Docker Desktop), Windows (Docker Desktop or Podman). Images come from ghcr.io/shsingh/opensase-*.

One manual step before OpenVPN starts: the openvpn_priv volume must contain a server.conf + PKI. With Nix available anywhere on your network:

nix run .#vpn-init          # writes ./state/openvpn
docker volume create opensase_openvpn_priv
docker run --rm -v "$PWD/state/openvpn:/src" -v opensase_openvpn_priv:/dst \
  alpine cp -a /src/. /dst/
docker compose -p opensase up -d --force-recreate openvpn

Container-only (no Nix anywhere): run EasyRSA inside the openvpn image against a scratch mount, or supply your own PKI — server.conf at /data-priv/server.conf.

Path 2 — NixOS appliance

git clone https://github.com/shsingh/opensase && cd opensase
nix run .#vpn-init
nixos-rebuild switch --flake .#opensase            # on the machine
nixos-rebuild switch --flake .#opensase --target-host root@<ip>   # remotely

nixosConfigurations.opensase targets aarch64-linux, .#opensase-x86_64 the other arch. The appliance module composes stock services — import nix/appliance.nix into an existing host config if you want the edge on a box that already runs NixOS.

Cert material lives in /var/lib/opensase/openvpn; the openvpn-opensase unit has a ConditionPathExists on server.conf, so an un-bootstrapped machine boots clean and everything except the VPN comes up.

Path 3 — QEMU dev VM

nix build .#vm-x86_64 && ./result/bin/run-opensase-vm    # aarch64: .#vm-aarch64

Needs KVM — a Linux host. Minimal-evaluation target: boots the full appliance, decision log included.

Kubernetes

No prebuilt manifests yet — a kustomize follow-up is tracked in the README Status list. The images stage on Kubernetes with this contract:

Container Image Capabilities Volume
dnsmasq opensase-dnsmasq — RWO for /var/lib/misc
clamav opensase-clamav — RWO for /var/lib/clamav (signature DB)
mitmproxy opensase-mitmproxy — RWO for /data (decision log)
openvpn opensase-openvpn NET_ADMIN RWO for /data-priv (server.conf + PKI)
  • Services: LoadBalancer or NodePort for udp/5443 (VPN) and tcp/53 (DNS); mitmproxy 8080 as ClusterIP (explicit-proxy clients) or LoadBalancer.
  • PKI: server.conf + CA/certs must exist under /data-priv before openvpn starts — inject via Secret with a postStart copy, or a protected volume. The k8s follow-up adds a readiness gate on this.
  • Transparent mode: unsupported on Kubernetes. TPROXY interception needs CNI-level support; run mitmproxy as an explicit proxy (8080) behind a Service instead.

Cloud

tofu/ carries a pinned OpenTofu skeleton (nix run .#tofu -- -chdir=tofu plan). Provision with your provider of choice, deploy with nixos-rebuild --target-host; provider shapes are tracked as an open item.