OpenFGA is one of the most widely adopted relationship-based authorization services, and this project shows how to host that service on the newest cloud tech: Unikraft unikernels. Everything in the repo—from the Docker Compose stack to the Nix dev shell—keeps the container and unikernel story perfectly aligned so you can iterate locally and then push the identical build to Unikraft Cloud.
- Overview
- Directory Layout
- Prerequisites
- Local Smoke Test
- OpenFGA Local Setup
- Authorization Models
- Unikraft Cloud Deployment
- Reference
- Reproducible tooling powered by
flake.nix, pinning OpenFGA CLI, KraftKit, and supporting utilities. - Local OpenFGA stack (PostgreSQL + OpenFGA + Caddy) that mirrors the configuration deployed to Unikraft Cloud.
- Modular authorization models, tests, and walkthroughs that highlight why OpenFGA remains a popular choice for ReBAC.
authz/– Local development stack (Docker Compose) and authorization models.authz/models/– FGA modules (projects.fga,tasks.fga) and tests.infrastructure/kraftcloud/– Kraft Cloud deployment assets.openfga/– OpenFGA Kraftfile and rootfs.caddy/– Caddy Kraftfile and config.docker-compose.yaml– Local validation stack matching the unikernels.
Dockerfile.openfga– Build definition for the OpenFGA unikernel.Dockerfile.caddy– Build definition for the Caddy unikernel.
-
Docker: Required for running the local stacks via Docker Compose.
- Verify:
docker --version && docker compose version
- Verify:
-
Nix: (Recommended) Enter the dev shell for consistent tooling.
- Install Nix.
- Run:
nix develop(ornix develop -c zsh).
Export your Unikraft Cloud token and target metro before deploying:
export UKC_TOKEN="<your-kraftcloud-token>"
export UKC_METRO=fraUse Docker Compose to confirm the build boots with your .env configuration. This uses the same Dockerfiles as the cloud deployment.
cp infrastructure/kraftcloud/.env.prod.example infrastructure/kraftcloud/.env
docker compose \
-f infrastructure/kraftcloud/docker-compose.yaml \
--env-file infrastructure/kraftcloud/.env \
up --buildEnsure you have a PostgreSQL instance running (e.g., from the authz/docker-compose.yaml stack) and configured in .env if testing full functionality.
This is the reference deployment for development. It uses standard container images to prove out model changes and CLI flows.
-
Create the environment configuration:
cp authz/.env.example authz/.env
-
Start the stack:
docker compose -f authz/docker-compose.yaml up -d
- API:
http://localhost:8080 - Playground:
http://localhost:8082/playground
- API:
-
Load the model:
export STORE_ID=01KA43FJDTE8AQCYZ6252ZR9HS export FGA_API_URL=http://localhost:8080 export FGA_API_TOKEN=dev-key-1 fga model write \ --store-id=$STORE_ID \ --api-url=$FGA_API_URL \ --api-token=$FGA_API_TOKEN \ --file authz/models/fga.mod
OpenFGA stays popular because its modeling experience scales, so the repo keeps the canonical modules in authz/models/ and runs them identically on Docker and Unikraft targets.
authz/models/fga.mod: Manifest that enumerates included modules.authz/models/projects.fga: Users, projects, and lists with hierarchical sharing.authz/models/tasks.fga: Tasks that inherit rights from their parent lists.
Export the helper variables once per shell session so the CLI examples just work:
export STORE_ID=01KA43FJDTE8AQCYZ6252ZR9HS
export FGA_API_URL=http://localhost:8080
export FGA_API_TOKEN=dev-key-1fga model write \
--store-id=$STORE_ID \
--api-url=$FGA_API_URL \
--api-token=$FGA_API_TOKEN \
--file authz/models/fga.modThe CLI prints the newly created authorization_model_id. This is the version identifier for the model you just wrote—keep using the same STORE_ID for all other commands, and optionally pass --authorization-model-id when you want to inspect an older version.
fga model get \
--store-id=$STORE_ID \
--api-url=$FGA_API_URL \
--api-token=$FGA_API_TOKENRun from the repository root so the relative paths resolve correctly:
fga model test --tests authz/models/projects.fga.yaml authz/models/tasks.fga.yamlfga model transform \
--input ./authz/models/projects.fga \
--output ./authz/models/projects.json- Create a
.fgamodule inauthz/models/. - Declare the module and add your relationships.
- Append the file path to
authz/models/fga.mod. - Add tests (
*.fga.yaml) beside the module. - Redeploy with
fga model write.
Deploy the exact same stack to Unikraft Cloud unikernels.
-
Copy the env file and configure your domain.
cp infrastructure/kraftcloud/.env.prod.example infrastructure/kraftcloud/.env export SUBDOMAIN=iapacte-openfga # Pick a unique subdomain export DOMAIN="$SUBDOMAIN.$UKC_METRO.unikraft.app" # Update .env with your domain perl -i -pe "s|^OPENFGA_API_HOST=.*|OPENFGA_API_HOST=$DOMAIN|" infrastructure/kraftcloud/.env perl -i -pe "s|^OPENFGA_PLAYGROUND_HOST=.*|OPENFGA_PLAYGROUND_HOST=$DOMAIN|" infrastructure/kraftcloud/.env
-
Load the env vars whenever you deploy:
set -a; . infrastructure/kraftcloud/.env; set +a
Deploy the backend server first to establish the internal network.
kraft cloud deploy \
--kraftfile infrastructure/kraftcloud/openfga/Kraftfile \
-M 1G \
.Capture the OpenFGA IP and update .env:
OPENFGA_INSTANCE=$(kraft cloud instance ls --metro $UKC_METRO -o json | jq -r 'sort_by(.created_at) | last | .name')
OPENFGA_IP=$(kraft cloud instance get $OPENFGA_INSTANCE --metro $UKC_METRO -o json | jq -r '.private_ip')
# Update .env
perl -i -pe "s|^OPENFGA_UPSTREAM_HOST=.*|OPENFGA_UPSTREAM_HOST=$OPENFGA_IP|" infrastructure/kraftcloud/.env
# Reload env
set -a; . infrastructure/kraftcloud/.env; set +aDeploy the reverse proxy to expose the service.
kraft cloud deploy \
--kraftfile infrastructure/kraftcloud/caddy/Kraftfile \
--subdomain "$SUBDOMAIN" \
-p 80:443/http+redirect \
-p 443:8080/http+tls \
-p 8443:8082/tls \
-M 512M \
.-p 80:443/http+redirect: Redirects HTTP traffic to HTTPS.-p 443:8080/http+tls: Exposes the OpenFGA API on the main domain (HTTPS).-p 8443:8082/tls: Exposes the Playground on port 8443 (TLS).
-
View services:
kraft cloud service ls --metro $UKC_METRO -
Tail logs:
kraft cloud instance logs <instance> --metro $UKC_METRO -
Verify Health:
curl -i "https://$DOMAIN/healthz"
Remove instances and services when you are done to avoid costs.
Remove instances:
# List instances
kraft cloud instance ls --metro $UKC_METRO
# Remove specific instance
kraft cloud instance remove <instance-name> --metro $UKC_METRORemove services:
# List services
kraft cloud service ls --metro $UKC_METRO
# Remove specific service
kraft cloud service remove <service-name> --metro $UKC_METRO