Setup Guide - Mic92/niks3 GitHub Wiki

niks3 Setup Guide

Complete instructions for setting up niks3.

Prerequisites

Deployment Options

  • NixOS Module - Recommended for NixOS users
  • Docker - For Kubernetes, NAS systems, or non-NixOS environments

Quick Start

For most users, we recommend Cloudflare R2 as it offers the best combination of performance and cost. See the S3 Provider Comparison page for alternatives.

NixOS Module Configuration

{
  services.niks3 = {
    enable = true;
    httpAddr = "127.0.0.1:5751";

    # S3 configuration
    s3 = {
      endpoint = "s3.amazonaws.com";  # or your S3-compatible endpoint
      bucket = "my-nix-cache";
      useSSL = true;
      accessKeyFile = "/run/secrets/s3-access-key";
      secretKeyFile = "/run/secrets/s3-secret-key";
    };

    # API authentication token (minimum 36 characters)
    apiTokenFile = "/run/secrets/niks3-api-token";

    # Signing keys for NAR signing
    signKeyFiles = [ "/run/secrets/niks3-signing-key" ];

    # Public cache URL (optional) - if exposed via https
    # Generates a landing page with usage instructions and public keys
    # cacheUrl = "https://cache.example.com";

    # Nginx reverse proxy (optional)
    nginx = {
      enable = true;
      # Domain for the niks3 server, not for the binary cache.
      # This is used by `niks3 push`
      domain = "niks3.example.com";
      # enableACME = true;      # default
      # forceSSL = true;        # default
    };
  };
}

Generating Secrets

niks3 requires two secrets:

  • API token: For authenticating niks3 push and niks3 gc commands (minimum 36 characters)
  • Signing key: For cryptographically signing NAR files

Using sops-nix (Recommended)

Generate the secrets locally and add them to your sops-encrypted secrets file:

# Generate API token
openssl rand -base64 32

# Generate signing key
nix key generate-secret --key-name my-cache-1

Add to your sops secrets file (e.g., secrets.yaml):

niks3-api-token: <generated-token>
niks3-signing-key: "my-cache-1:base64secretkey..."
niks3-s3-access-key: <your-s3-access-key>
niks3-s3-secret-key: <your-s3-secret-key>

Configure sops-nix in your NixOS configuration:

{
  sops.secrets.niks3-api-token.owner = "niks3";
  sops.secrets.niks3-signing-key.owner = "niks3";
  sops.secrets.niks3-s3-access-key.owner = "niks3";
  sops.secrets.niks3-s3-secret-key.owner = "niks3";

  services.niks3 = {
    enable = true;
    apiTokenFile = config.sops.secrets.niks3-api-token.path;
    signKeyFiles = [ config.sops.secrets.niks3-signing-key.path ];
    s3 = {
      accessKeyFile = config.sops.secrets.niks3-s3-access-key.path;
      secretKeyFile = config.sops.secrets.niks3-s3-secret-key.path;
      # ... other s3 config
    };
  };
}

Without sops-nix

Store secrets in a persistent location with proper permissions:

# Create secrets directory
sudo mkdir -p /var/lib/niks3/secrets
sudo chown niks3:niks3 /var/lib/niks3/secrets
sudo chmod 700 /var/lib/niks3/secrets

# Generate and store API token
openssl rand -base64 32 | sudo tee /var/lib/niks3/secrets/api-token > /dev/null
sudo chmod 600 /var/lib/niks3/secrets/api-token
sudo chown niks3:niks3 /var/lib/niks3/secrets/api-token

# Generate and store signing key
nix key generate-secret --key-name my-cache-1 | sudo tee /var/lib/niks3/secrets/signing-key > /dev/null
sudo chmod 600 /var/lib/niks3/secrets/signing-key
sudo chown niks3:niks3 /var/lib/niks3/secrets/signing-key

Reference in your NixOS configuration:

{
  services.niks3 = {
    enable = true;
    apiTokenFile = "/var/lib/niks3/secrets/api-token";
    signKeyFiles = [ "/var/lib/niks3/secrets/signing-key" ];
    s3 = {
      accessKeyFile = "/var/lib/niks3/secrets/s3-access-key";
      secretKeyFile = "/var/lib/niks3/secrets/s3-secret-key";
      # ... other s3 config
    };
  };
}

Extracting Public Key

To configure Nix clients to trust your cache, extract the public key:

nix key convert-secret-to-public < /path/to/signing-key
# Output: my-cache-1:base64encodedpublickey...

Configure Nix clients to trust the public key:

{
  nix.settings.trusted-public-keys = [
    "my-cache-1:base64encodedpublickey..."
  ];
}

Client Usage

Pushing Store Paths

export NIKS3_SERVER_URL=http://server:5751
export NIKS3_AUTH_TOKEN_FILE=/path/to/token-file

niks3 push /nix/store/...-package-name

The push operation uploads:

  • NAR (compressed with zstd)
  • Signed narinfo
  • Build logs (if available)
  • Realisation info for content-addressed derivations

Pulling from Cache

Use Nix's native S3 support:

export AWS_ACCESS_KEY_ID=your-access-key
export AWS_SECRET_ACCESS_KEY=your-secret-key

nix copy --from 's3://my-nix-cache?endpoint=http://localhost:9000&region=us-east-1' \
         /nix/store/...-package-name

Signatures are verified automatically using configured trusted public keys.

Tip: If your S3 bucket is private and you don't want to distribute S3 credentials to clients, enable the built-in read proxy instead. Clients then use the niks3 server URL as their substituter: nix copy --from 'http://my-niks3-server:5751' ...

Viewing Build Logs

nix log --store 's3://my-nix-cache?endpoint=http://localhost:9000&region=us-east-1' \
        /nix/store/...-package-name

Garbage Collection

niks3 implements reference-tracking garbage collection to clean up old closures and unreachable objects from the cache.

Running Garbage Collection

export NIKS3_SERVER_URL=http://server:5751
export NIKS3_AUTH_TOKEN_FILE=/path/to/token-file

# Delete closures older than 30 days
niks3 gc --older-than=720h

The GC process runs in three phases:

  1. Clean up failed uploads: Removes incomplete uploads older than --failed-uploads-older-than (default: 6h)
  2. Delete old closures: Removes closures older than --older-than
  3. Mark and delete orphaned objects: Marks unreachable objects, then deletes them after a grace period

Statistics Output

The GC command logs detailed statistics:

INFO Starting garbage collection older-than=720h failed-uploads-older-than=6h force=false
INFO Garbage collection completed successfully failed-uploads-deleted=5 old-closures-deleted=142 objects-marked-for-deletion=1523 objects-deleted-after-grace-period=1520 objects-failed-to-delete=3

Statistics explained:

  • failed-uploads-deleted: Number of incomplete/failed uploads cleaned up
  • old-closures-deleted: Number of closures older than the threshold that were removed
  • objects-marked-for-deletion: Number of unreachable objects marked as deleted (first phase)
  • objects-deleted-after-grace-period: Number of objects actually removed from S3 and database after the grace period
  • objects-failed-to-delete: Number of objects that couldn't be deleted from S3 and were marked active again

Grace Period

The grace period (default: same as --failed-uploads-older-than) prevents race conditions during concurrent uploads. Objects are marked for deletion first, then deleted only after the grace period has elapsed. This ensures that objects from in-flight uploads are not prematurely deleted.

Force Mode (Dangerous)

# WARNING: Immediate deletion without grace period
niks3 gc --older-than=720h --force

Force mode bypasses the grace period and deletes objects immediately. Only use this when no uploads are in progress, as it may delete objects that are currently being uploaded or referenced.

Automatic Garbage Collection (NixOS Module)

The NixOS module includes automatic garbage collection via a systemd timer:

{
  services.niks3 = {
    enable = true;
    # ... other configuration ...

    gc = {
      enable = true;                      # Default: true
      olderThan = "720h";                 # 30 days (default)
      failedUploadsOlderThan = "6h";      # 6 hours (default)
      schedule = "daily";                 # Run at midnight daily (default)
      randomizedDelaySec = 1800;          # Add 0-30 min random delay (default)
    };
  };
}

Options:

  • gc.enable: Enable/disable automatic garbage collection (default: true)
  • gc.olderThan: How old closures must be before deletion (default: "720h" = 30 days)
    • Examples: "168h" (7 days), "2160h" (90 days)
  • gc.failedUploadsOlderThan: How old failed uploads must be before cleanup (default: "6h" = 6 hours)
    • Examples: "12h" (12 hours), "24h" (1 day)
  • gc.schedule: When to run GC in systemd calendar format (default: "daily")
    • Examples: "weekly", "*-*-* 02:00:00" (daily at 2 AM), "Sun *-*-* 03:00:00" (Sundays at 3 AM)
  • gc.randomizedDelaySec: Random delay in seconds before starting (default: 1800 = 30 minutes)
    • Helps distribute load across multiple instances

The automatic GC runs as a systemd service (niks3-gc.service) triggered by a timer (niks3-gc.timer). View logs with:

# View GC service logs
journalctl -u niks3-gc.service

# Check next scheduled run
systemctl list-timers niks3-gc.timer

# Run GC manually
systemctl start niks3-gc.service

Provider-Specific Configuration Examples

Cloudflare R2

{
  services.niks3.s3 = {
    endpoint = "<account-id>.r2.cloudflarestorage.com";
    bucket = "nix-cache";
    region = "auto";  # R2 uses "auto" for region
    useSSL = true;
    accessKeyFile = "/run/secrets/r2-access-key";
    secretKeyFile = "/run/secrets/r2-secret-key";
  };
}

Backblaze B2

{
  services.niks3.s3 = {
    endpoint = "s3.eu-central-003.backblazeb2.com";
    bucket = "my-nix-cache";
    region = "eu-central-003";
    useSSL = true;
    accessKeyFile = "/run/secrets/b2-key-id";
    secretKeyFile = "/run/secrets/b2-application-key";
  };
}

Note: Backblaze B2 requires a CDN (like Fastly) to set Content-Encoding headers. See the clan-infra example.

AWS S3

{
  services.niks3.s3 = {
    endpoint = "s3.us-east-1.amazonaws.com";
    bucket = "my-nix-cache";
    region = "us-east-1";
    useSSL = true;
    accessKeyFile = "/run/secrets/aws-access-key";
    secretKeyFile = "/run/secrets/aws-secret-key";
  };
}

Docker

For environments where NixOS isn't available (Kubernetes, NAS systems, generic container hosts), niks3 provides multi-architecture Docker images.

Image

ghcr.io/mic92/niks3

Available tags:

  • main - Latest from main branch
  • v1.2.3 - Specific version
  • v1.2, v1 - Major/minor version aliases

Architectures: linux/amd64, linux/arm64

Environment Variables

Variable Required Description
NIKS3_DB Yes PostgreSQL connection string
NIKS3_HTTP_ADDR No Listen address (default: :5751)
NIKS3_S3_ENDPOINT Yes S3 endpoint URL
NIKS3_S3_BUCKET Yes S3 bucket name
NIKS3_S3_ACCESS_KEY Yes S3 access key
NIKS3_S3_SECRET_KEY Yes S3 secret key
NIKS3_S3_REGION No S3 region override (e.g., auto for R2)
NIKS3_S3_USE_SSL No Use HTTPS for S3 (default: true)
NIKS3_API_TOKEN Yes API authentication token (min 36 chars)
NIKS3_SIGN_KEY_PATHS Yes Colon-separated paths to signing key files

Example: Docker Compose

services:
  niks3:
    image: ghcr.io/mic92/niks3:main
    ports:
      - "5751:5751"
    environment:
      NIKS3_DB: postgres://niks3:password@db:5432/niks3?sslmode=disable
      NIKS3_S3_ENDPOINT: account-id.r2.cloudflarestorage.com
      NIKS3_S3_BUCKET: nix-cache
      NIKS3_S3_ACCESS_KEY: ${S3_ACCESS_KEY}
      NIKS3_S3_SECRET_KEY: ${S3_SECRET_KEY}
      NIKS3_API_TOKEN: ${API_TOKEN}
      NIKS3_SIGN_KEY_PATHS: /secrets/signing-key
    volumes:
      - ./secrets:/secrets:ro
    depends_on:
      - db

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: niks3
      POSTGRES_PASSWORD: password
      POSTGRES_DB: niks3
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

Example: Docker Run

docker run -d \
  -p 5751:5751 \
  -v ./secrets:/secrets:ro \
  -e NIKS3_DB="postgres://user:pass@host:5432/niks3" \
  -e NIKS3_S3_ENDPOINT="account-id.r2.cloudflarestorage.com" \
  -e NIKS3_S3_BUCKET="nix-cache" \
  -e NIKS3_S3_ACCESS_KEY="your-access-key" \
  -e NIKS3_S3_SECRET_KEY="your-secret-key" \
  -e NIKS3_API_TOKEN="your-api-token-min-36-chars" \
  -e NIKS3_SIGN_KEY_PATHS="/secrets/signing-key" \
  ghcr.io/mic92/niks3:main

Notes

  • The image includes busybox for debugging (docker exec -it <container> sh)
  • Database migrations run automatically on startup
  • For production, use proper secrets management instead of environment variables

Troubleshooting

Permission Issues

Ensure your S3 credentials have the following permissions:

  • s3:PutObject
  • s3:GetObject
  • s3:DeleteObject
  • s3:ListBucket

Database Connection

The PostgreSQL database is automatically configured by the NixOS module. If you encounter issues, check:

systemctl status postgresql
journalctl -u niks3

Network Issues

Test S3 connectivity:

# Using AWS CLI
aws s3 ls s3://your-bucket --endpoint-url=https://your-endpoint

# Using curl
curl -I https://your-bucket.your-endpoint/
⚠️ **GitHub.com Fallback** ⚠️