Setup Guide - Mic92/niks3 GitHub Wiki
Complete instructions for setting up niks3.
- S3-compatible storage - see S3 Provider Comparison for recommendations
- PostgreSQL database
- Nix signing keys
- NixOS Module - Recommended for NixOS users
- Docker - For Kubernetes, NAS systems, or non-NixOS environments
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.
{
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
};
};
}niks3 requires two secrets:
-
API token: For authenticating
niks3 pushandniks3 gccommands (minimum 36 characters) - Signing key: For cryptographically signing NAR files
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-1Add 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
};
};
}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-keyReference 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
};
};
}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..."
];
}export NIKS3_SERVER_URL=http://server:5751
export NIKS3_AUTH_TOKEN_FILE=/path/to/token-file
niks3 push /nix/store/...-package-nameThe push operation uploads:
- NAR (compressed with zstd)
- Signed narinfo
- Build logs (if available)
- Realisation info for content-addressed derivations
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®ion=us-east-1' \
/nix/store/...-package-nameSignatures 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' ...
nix log --store 's3://my-nix-cache?endpoint=http://localhost:9000®ion=us-east-1' \
/nix/store/...-package-nameniks3 implements reference-tracking garbage collection to clean up old closures and unreachable objects from the cache.
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=720hThe GC process runs in three phases:
-
Clean up failed uploads: Removes incomplete uploads older than
--failed-uploads-older-than(default: 6h) -
Delete old closures: Removes closures older than
--older-than - Mark and delete orphaned objects: Marks unreachable objects, then deletes them after a grace period
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
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.
# WARNING: Immediate deletion without grace period
niks3 gc --older-than=720h --forceForce 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.
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)
- Examples:
-
gc.failedUploadsOlderThan: How old failed uploads must be before cleanup (default:"6h"= 6 hours)- Examples:
"12h"(12 hours),"24h"(1 day)
- Examples:
-
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)
- Examples:
-
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{
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";
};
}{
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.
{
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";
};
}For environments where NixOS isn't available (Kubernetes, NAS systems, generic container hosts), niks3 provides multi-architecture Docker images.
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
| 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 |
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: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- The image includes
busyboxfor debugging (docker exec -it <container> sh) - Database migrations run automatically on startup
- For production, use proper secrets management instead of environment variables
Ensure your S3 credentials have the following permissions:
s3:PutObjects3:GetObjects3:DeleteObjects3:ListBucket
The PostgreSQL database is automatically configured by the NixOS module. If you encounter issues, check:
systemctl status postgresql
journalctl -u niks3Test 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/