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"]
Deployment
Three paths, one source of truth (the flake). Match the path to the host:
Path 1 — Docker compose (no Nix, any OS)
git clone https://github.com/shsingh/opensase && cd opensase
docker compose -p opensase up -dWorks 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 openvpnContainer-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> # remotelynixosConfigurations.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-aarch64Needs 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:
LoadBalancerorNodePortforudp/5443(VPN) andtcp/53(DNS); mitmproxy8080as ClusterIP (explicit-proxy clients) or LoadBalancer. - PKI:
server.conf+ CA/certs must exist under/data-privbeforeopenvpnstarts — 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.