NixOS
Run SparkyFitness on NixOS without Docker using the Nix flake and the
services.sparkyfitness NixOS module. The module runs the backend as a systemd
service, optionally provisions a local PostgreSQL database, and serves the
static frontend through nginx with the same reverse-proxy routes (/api,
/health-data, /uploads, /mcp) the Docker deployment uses.
Prerequisites
- A NixOS host with flakes enabled (
nix.settings.experimental-features = [ "nix-command" "flakes" ];). - PostgreSQL 15 or later. The module defaults to
postgresql_16. Earlier versions reject the migrations (which useUNIQUE NULLS NOT DISTINCT) with a syntax error near"NULLS".
1. Add the flake input
Add SparkyFitness as a flake input and import its NixOS module in your system configuration:
{
inputs.sparkyfitness.url = "github:CodeWithCJ/SparkyFitness";
outputs = { nixpkgs, sparkyfitness, ... }: {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
sparkyfitness.nixosModules.sparkyfitness
{
services.sparkyfitness = {
enable = true;
frontendUrl = "https://fitness.example.com";
# Secrets (DB passwords, encryption key, auth secret) live here:
environmentFile = "/run/secrets/sparkyfitness.env";
nginx.virtualHost = "fitness.example.com";
};
}
];
};
};
}
Use nixosModules.sparkyfitness to wire in this flake's package builds
automatically, or nixosModules.default and set backendPackage /
frontendPackage yourself.
2. Provide the secrets
environmentFile is a systemd EnvironmentFile and should contain at least:
SPARKY_FITNESS_DB_PASSWORD=...
SPARKY_FITNESS_APP_DB_PASSWORD=...
SPARKY_FITNESS_API_ENCRYPTION_KEY=... # openssl rand -hex 32
BETTER_AUTH_SECRET=...
Keep this file out of the Nix store (e.g. via sops-nix or agenix). When
database.createLocally = true (the default), SPARKY_FITNESS_DB_PASSWORD is
also used to provision the local PostgreSQL owner role.
3. Build and switch
sudo nixos-rebuild switch --flake .#myhost
On first activation the module:
- provisions the local PostgreSQL owner role and database (when
database.createLocallyis enabled); - starts the backend systemd service, which runs migrations and creates the limited application role automatically;
- serves the frontend through nginx on the configured virtual host.
Key options
| Option | Default | Description |
|---|---|---|
enable | false | Enable the SparkyFitness service. |
frontendUrl | (required) | Public URL of the site (CORS / Better Auth trusted origins). |
environmentFile | (required) | Path to the systemd EnvironmentFile holding the secrets above. |
port | 3010 | Backend API listen port. |
stateDir | /var/lib/sparkyfitness | Persistent state (uploads, backups, temp uploads). |
database.createLocally | true | Provision a local PostgreSQL instance, owner role and database. |
database.package | pkgs.postgresql_16 | PostgreSQL package for the local instance (must be >= 15). |
nginx.enable | true | Serve the frontend and reverse-proxy the API through nginx. |
nginx.virtualHost | localhost | nginx virtual host name. |
extraEnvironment | {} | Additional environment variables for the backend. |
To point at an external database instead, set database.createLocally = false
and configure database.host, database.port, database.name,
database.user and the corresponding passwords in environmentFile.
Local build and development
nix build .#sparkyfitness-server
nix build .#sparkyfitness-frontend
nix develop # dev shell with node 24, pnpm and postgresql
The pnpmDeps.hash values pinned in the flake must be refreshed whenever
pnpm-lock.yaml changes (a stale hash surfaces as a hash mismatch build
failure). Run the helper script to recompute them:
nix/update-hashes.sh
# or, if nix is not on PATH:
NIX=/path/to/nix nix/update-hashes.sh
See nix/README.md
in the repository for the full layout and additional detail.
