Nix flake that makes provisioning and managing Forgejo servers easy!
  • Nix 86.2%
  • HCL 13.8%
Find a file
2026-05-09 10:34:49 -07:00
nixos Initial import: forgejo-setup from nixcfg 2026-05-09 10:34:49 -07:00
terraform Initial import: forgejo-setup from nixcfg 2026-05-09 10:34:49 -07:00
.gitignore Initial import: forgejo-setup from nixcfg 2026-05-09 10:34:49 -07:00
config.nix.example Initial import: forgejo-setup from nixcfg 2026-05-09 10:34:49 -07:00
flake.lock Initial import: forgejo-setup from nixcfg 2026-05-09 10:34:49 -07:00
flake.nix Initial import: forgejo-setup from nixcfg 2026-05-09 10:34:49 -07:00
Makefile.example Initial import: forgejo-setup from nixcfg 2026-05-09 10:34:49 -07:00
README.md Initial import: forgejo-setup from nixcfg 2026-05-09 10:34:49 -07:00

forgejo-setup

A reusable Nix function that produces a complete, deployable Forgejo server configuration on DigitalOcean.

Given a host's config.nix and secrets/secrets.yaml, forgejo-setup generates everything needed to provision infrastructure and deploy NixOS:

  • Terraform config — droplet, volume, firewall, DNS records
  • NixOS config — Forgejo service, Caddy reverse proxy, system config
  • sops-nix template files — configs with placeholders for decrypted secrets

Usage

# hosts/your-instance/flake.nix
{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
    flake-utils.url = "github:numtide/flake-utils";
    sops-nix.url = "github:Mic92/sops-nix";
  };

  outputs = { self, nixpkgs, flake-utils, sops-nix }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        forgejo-setup = import ../../packages/forgejo-setup {
          nixpkgs = nixpkgs;
          sops-nix = sops-nix;
          hostConfig = ./config.nix;
          secretsFile = ./secrets/secrets.yaml;
        };
      in {
        packages = { inherit (forgejo-setup) templates; };
      }
    );
}

Then copy the example Makefile and customize for your host:

cp ../../packages/forgejo-setup/Makefile.example Makefile
# Edit the help text at the top to match your host name

Build and substitute:

nix build .#templates                            # pure build → result/ with placeholders
sops-install-secrets ./result/manifest.json      # substitute secrets
cp -rL result/* ./                               # copy to terraform/ and nixos/

Or using the Makefile:

make substitute-sops-secrets  # build + substitute + copy
make plan                     # terraform plan
make deploy-nixos             # substitute + rsync + nixos-rebuild

Configuration Schema

config.nix requires the following fields:

{
  domain = "example.com";
  subdomain = "git";                  # becomes git.example.com
  hostName = "forgejo";               # droplet hostname
  dropletSize = "s-1vcpu-1gb";
  dropletRegion = "nyc3";
  dropletImage = "nixos-base";
  dropletSshKeyName = "my-ssh-key";
  customImageUrl = "https://...";
  volumeSize = 10;                     # GiB
  volumeName = "forgejo-data";         # DigitalOcean volume name
  enableBackups = true;
  s3BucketName = "my-instance-terraform-state";
  s3Endpoint = "https://s3.us-west-001.backblazeb2.com";
  s3BucketRegion = "us-west-001";
  forgejoSiteTitle = "My Forgejo";
  forgejoSiteDescription = "Self-hosted Git service";

  # Optional: Forgejo data directory (default: /mnt/forgejo-data/forgejo)
  # forgejoDataDir = "/mnt/forgejo-data/forgejo";
}

Secrets

Encrypted in secrets/secrets.yaml via sops:

  • digitalocean_token — DigitalOcean API token
  • cloudflare_api_token — Cloudflare API token (for DNS)
  • cloudflare_zone_id — Cloudflare zone ID
  • letsencrypt_email — Email for Let's Encrypt certificates
  • forgejo_admin_email — Admin email for Forgejo
  • s3_access_key_id — AWS-style access key for S3-compatible Terraform backend (e.g. Backblaze B2) Used by Terraform's S3 backend for remote state. Exported as AWS_ACCESS_KEY_ID.
  • s3_secret_access_key — AWS-style secret key for S3-compatible Terraform backend Exported as AWS_SECRET_ACCESS_KEY.

What's Produced

Running nix build .#templates produces:

result/
├── manifest.json                 # for sops-install-secrets
├── nixos/
│   ├── flake.nix                 # NixOS flake for the droplet
│   ├── configuration.nix         # base NixOS config (hostname, volume)
│   ├── forgejo.nix               # Forgejo service module
│   ├── caddy.nix                 # Caddy reverse proxy module
│   ├── droplet-vars.nix          # template (placeholders for secrets)
│   └── secrets.env               # template (API tokens for snapshot daemon)
└── terraform/
    ├── main.tf                   # infrastructure definition
    ├── variables.tf               # shared
    ├── providers.tf               # shared
    ├── outputs.tf                 # shared
    └── terraform.auto.tfvars.json # template (all vars with placeholders)

After sops-install-secrets, the template files have real decrypted values.

Terraform Remote State

Terraform is configured to use an S3-compatible backend for remote state storage (via the generated backend.tf). The bucket is per-instance, configured in config.nix as s3BucketName. Credentials (s3_access_key_id and s3_secret_access_key) are decrypted from sops and exported as AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY by the host's .envrc.

One-time setup

  1. Create the S3-compatible bucket (e.g. Backblaze B2) with the name matching s3BucketName in your config.nix.

  2. Generate an application key restricted to that bucket (read/write).

  3. Add credentials to sops:

    sops secrets/secrets.yaml
    

    Add the following fields:

    s3_access_key_id: "your_access_key_id"
    s3_secret_access_key: "your_secret_access_key"
    
  4. Migrate local state to the remote backend:

    cd terraform
    terraform init -migrate-state
    

After migration, terraform plan and terraform apply use remote state automatically. The local terraform.tfstate becomes a local cache synced with the remote backend and can be safely deleted.

Design

forgejo-setup uses sops-nix templates: non-secret values are inlined from config.nix at build time, while secret values use placeholders (<SOPS:sha256:PLACEHOLDER>). A separate sops-install-secrets step decrypts secrets.yaml and substitutes placeholders with real values.

The derivation is fully pure — no --impure or builtins.getEnv needed.