Files
gitea-mirror/docs/NIX_DEPLOYMENT.md
T
ARUNAVO RAYandGitHub 2d1795051d docs(nix): import the module before enabling services.gitea-mirror (#434)
The README, NIX.md and the production snippet in the deployment guide set
services.gitea-mirror straight after adding the flake input, but the
option only exists once gitea-mirror.nixosModules.default is imported.
Only the quick start snippet showed that (#426). Every snippet that
enables the service now shows the import and where the input goes.
2026-09-16 07:31:52 +05:30

13 KiB

Nix Deployment Guide

This guide covers deploying Gitea Mirror using Nix flakes. The Nix deployment follows the same minimal configuration philosophy as docker-compose.alt.yml - secrets are auto-generated, and everything else can be configured via the web UI.

Prerequisites

  • Nix 2.4+ installed
  • For NixOS module: NixOS 23.05+

To enable flakes permanently and avoid typing flags, add to /etc/nix/nix.conf or ~/.config/nix/nix.conf:

experimental-features = nix-command flakes

Note: If you don't enable flakes globally, add --extra-experimental-features 'nix-command flakes' to all nix commands shown below.

Quick Start (Zero Configuration!)

Run Immediately - No Setup Required

# Run directly from the flake (local)
nix run --extra-experimental-features 'nix-command flakes' .#gitea-mirror

# Or from GitHub (once published)
nix run --extra-experimental-features 'nix-command flakes' github:RayLabsHQ/gitea-mirror

# If you have flakes enabled globally, simply:
nix run .#gitea-mirror

That's it! On first run:

  • Secrets (BETTER_AUTH_SECRET and ENCRYPTION_SECRET) are auto-generated
  • Database is automatically created and initialized
  • Startup recovery and repair scripts run automatically
  • Access the web UI at http://localhost:4321

Everything else (GitHub credentials, Gitea settings, mirror options) is configured through the web interface after signup.

Development Environment

# Enter development shell with all dependencies
nix develop --extra-experimental-features 'nix-command flakes'

# Or use direnv for automatic environment loading (handles flags automatically)
echo "use flake" > .envrc
direnv allow

Build and Install

# Build the package
nix build --extra-experimental-features 'nix-command flakes'

# Run the built package
./result/bin/gitea-mirror

# Install to your profile
nix profile install --extra-experimental-features 'nix-command flakes' .#gitea-mirror

What Happens on First Run?

Following the same pattern as the Docker deployment, the Nix package automatically:

  1. Creates data directory: ~/.local/share/gitea-mirror (or $DATA_DIR)
  2. Generates secrets (stored securely in data directory):
    • BETTER_AUTH_SECRET - Session authentication (32-char hex)
    • ENCRYPTION_SECRET - Token encryption (48-char base64)
  3. Initializes database: SQLite database with Drizzle migrations
  4. Runs startup scripts:
    • Environment configuration loader
    • Crash recovery for interrupted jobs
    • Repository status repair
  5. Starts the application with graceful shutdown handling

NixOS Module - Minimal Deployment

Simplest Possible Configuration

Add to your NixOS configuration (/etc/nixos/configuration.nix):

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    gitea-mirror.url = "github:RayLabsHQ/gitea-mirror";
  };

  outputs = { nixpkgs, gitea-mirror, ... }: {
    nixosConfigurations.your-hostname = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        gitea-mirror.nixosModules.default
        {
          # That's it! Just enable the service
          services.gitea-mirror.enable = true;
        }
      ];
    };
  };
}

Apply with:

sudo nixos-rebuild switch

Access at http://localhost:4321, sign up (first user is admin), and configure everything via the web UI.

Production Configuration

For production with custom domain and firewall. Like every snippet on this page, it needs the module imported first; services.gitea-mirror does not exist until gitea-mirror.nixosModules.default is in your modules list.

{ inputs, ... }:
{
  imports = [ inputs.gitea-mirror.nixosModules.default ];

  services.gitea-mirror = {
    enable = true;
    host = "0.0.0.0";
    port = 4321;
    betterAuthUrl = "https://mirror.example.com";
    betterAuthTrustedOrigins = "https://mirror.example.com";
    openFirewall = true;
  };

  # Optional: Use with nginx reverse proxy
  services.nginx = {
    enable = true;
    virtualHosts."mirror.example.com" = {
      locations."/" = {
        proxyPass = "http://127.0.0.1:4321";
        proxyWebsockets = true;
      };
      enableACME = true;
      forceSSL = true;
    };
  };
}

Advanced: Manual Secret Management

If you prefer to manage secrets manually (e.g., with sops-nix or agenix):

  1. Create a secrets file:
# /var/lib/gitea-mirror/secrets.env
BETTER_AUTH_SECRET=your-32-character-minimum-secret-key-here
ENCRYPTION_SECRET=your-encryption-secret-here
  1. Reference it in your configuration:
{
  services.gitea-mirror = {
    enable = true;
    environmentFile = "/var/lib/gitea-mirror/secrets.env";
  };
}

Full Configuration Options

{
  services.gitea-mirror = {
    enable = true;
    package = gitea-mirror.packages.x86_64-linux.default;  # Override package
    dataDir = "/var/lib/gitea-mirror";
    user = "gitea-mirror";
    group = "gitea-mirror";
    host = "0.0.0.0";
    port = 4321;
    betterAuthUrl = "https://mirror.example.com";
    betterAuthTrustedOrigins = "https://mirror.example.com";

    # Concurrency controls (match docker-compose.alt.yml)
    mirrorIssueConcurrency = 3;  # Set to 1 for perfect chronological order
    mirrorPullRequestConcurrency = 5;  # Set to 1 for perfect chronological order

    environmentFile = null;  # Optional secrets file
    openFirewall = true;
  };
}

Service Management (NixOS)

# Start the service
sudo systemctl start gitea-mirror

# Stop the service
sudo systemctl stop gitea-mirror

# Restart the service
sudo systemctl restart gitea-mirror

# Check status
sudo systemctl status gitea-mirror

# View logs
sudo journalctl -u gitea-mirror -f

# Health check
curl http://localhost:4321/api/health

Environment Variables

All variables from docker-compose.alt.yml are supported:

# === AUTO-GENERATED (Don't set unless you want specific values) ===
BETTER_AUTH_SECRET          # Auto-generated, stored in data dir
ENCRYPTION_SECRET           # Auto-generated, stored in data dir

# === CORE SETTINGS (Have good defaults) ===
DATA_DIR="$HOME/.local/share/gitea-mirror"
DATABASE_URL="file:$DATA_DIR/gitea-mirror.db"
HOST="0.0.0.0"
PORT="4321"
NODE_ENV="production"

# === BETTER AUTH (Override for custom domains) ===
BETTER_AUTH_URL="http://localhost:4321"
BETTER_AUTH_TRUSTED_ORIGINS="http://localhost:4321"
PUBLIC_BETTER_AUTH_URL="http://localhost:4321"

# === CONCURRENCY CONTROLS ===
MIRROR_ISSUE_CONCURRENCY=3           # Default: 3 (set to 1 for perfect order)
MIRROR_PULL_REQUEST_CONCURRENCY=5    # Default: 5 (set to 1 for perfect order)

# === CONFIGURE VIA WEB UI (Not needed at startup) ===
# GitHub credentials, Gitea settings, mirror options, scheduling, etc.
# All configured after signup through the web interface

Database Management

The Nix package includes a database management helper:

# Initialize database (done automatically on first run)
gitea-mirror-db init

# Check database health
gitea-mirror-db check

# Fix database issues
gitea-mirror-db fix

# Reset users
gitea-mirror-db reset-users

Home Manager Integration

For single-user deployments:

{ config, pkgs, ... }:
let
  gitea-mirror = (import (fetchTarball "https://github.com/RayLabsHQ/gitea-mirror/archive/main.tar.gz")).packages.${pkgs.system}.default;
in {
  home.packages = [ gitea-mirror ];

  # Optional: Run as user service
  systemd.user.services.gitea-mirror = {
    Unit = {
      Description = "Gitea Mirror Service";
      After = [ "network.target" ];
    };

    Service = {
      Type = "simple";
      ExecStart = "${gitea-mirror}/bin/gitea-mirror";
      Restart = "always";
      Environment = [
        "DATA_DIR=%h/.local/share/gitea-mirror"
        "HOST=127.0.0.1"
        "PORT=4321"
      ];
    };

    Install = {
      WantedBy = [ "default.target" ];
    };
  };
}

Docker Image from Nix (Optional)

You can also use Nix to create a Docker image:

# Add to flake.nix packages section
dockerImage = pkgs.dockerTools.buildLayeredImage {
  name = "gitea-mirror";
  tag = "latest";
  contents = [ self.packages.${system}.default pkgs.cacert pkgs.openssl ];
  config = {
    Cmd = [ "${self.packages.${system}.default}/bin/gitea-mirror" ];
    ExposedPorts = { "4321/tcp" = {}; };
    Env = [
      "DATA_DIR=/data"
      "DATABASE_URL=file:/data/gitea-mirror.db"
    ];
    Volumes = { "/data" = {}; };
  };
};

Build and load:

nix build --extra-experimental-features 'nix-command flakes' .#dockerImage
docker load < result
docker run -p 4321:4321 -v gitea-mirror-data:/data gitea-mirror:latest

Comparison: Docker vs Nix

Both deployment methods follow the same philosophy:

Feature Docker Compose Nix
Configuration Minimal (only BETTER_AUTH_SECRET) Zero config (auto-generated)
Secret Generation Auto-generated & persisted Auto-generated & persisted
Database Init Automatic on first run Automatic on first run
Startup Scripts Runs recovery/repair/env-config Runs recovery/repair/env-config
Graceful Shutdown Signal handling in entrypoint Signal handling in wrapper
Health Check Docker healthcheck systemd timer (optional)
Updates docker pull nix flake update && nixos-rebuild

Troubleshooting

Check Auto-Generated Secrets

# For standalone
cat ~/.local/share/gitea-mirror/.better_auth_secret
cat ~/.local/share/gitea-mirror/.encryption_secret

# For NixOS service
sudo cat /var/lib/gitea-mirror/.better_auth_secret
sudo cat /var/lib/gitea-mirror/.encryption_secret

Database Issues

# Check if database exists
ls -la ~/.local/share/gitea-mirror/gitea-mirror.db

# Reinitialize (deletes all data!)
rm ~/.local/share/gitea-mirror/gitea-mirror.db
gitea-mirror-db init

Permission Issues (NixOS)

sudo chown -R gitea-mirror:gitea-mirror /var/lib/gitea-mirror
sudo chmod 700 /var/lib/gitea-mirror

Port Already in Use

# Change port
export PORT=8080
gitea-mirror

# Or in NixOS config
services.gitea-mirror.port = 8080;

View Startup Logs

# Standalone (verbose output on console)
gitea-mirror

# NixOS service
sudo journalctl -u gitea-mirror -f --since "5 minutes ago"

Updating

Standalone Installation

# Update flake lock
nix flake update --extra-experimental-features 'nix-command flakes'

# Rebuild
nix build --extra-experimental-features 'nix-command flakes'

# Or update profile
nix profile upgrade --extra-experimental-features 'nix-command flakes' gitea-mirror

NixOS

# Update input
sudo nix flake lock --update-input gitea-mirror --extra-experimental-features 'nix-command flakes'

# Rebuild system
sudo nixos-rebuild switch --flake .#your-hostname

Migration from Docker

To migrate from Docker to Nix while keeping your data:

  1. Stop Docker container:

    docker-compose -f docker-compose.alt.yml down
    
  2. Copy data directory:

    # For standalone
    cp -r ./data ~/.local/share/gitea-mirror
    
    # For NixOS
    sudo cp -r ./data /var/lib/gitea-mirror
    sudo chown -R gitea-mirror:gitea-mirror /var/lib/gitea-mirror
    
  3. Copy secrets (if you want to keep them):

    # Extract from Docker volume
    docker run --rm -v gitea-mirror_data:/data alpine \
      cat /data/.better_auth_secret > better_auth_secret
    docker run --rm -v gitea-mirror_data:/data alpine \
      cat /data/.encryption_secret > encryption_secret
    
    # Copy to new location
    cp better_auth_secret ~/.local/share/gitea-mirror/.better_auth_secret
    cp encryption_secret ~/.local/share/gitea-mirror/.encryption_secret
    chmod 600 ~/.local/share/gitea-mirror/.*_secret
    
  4. Start Nix version:

    gitea-mirror
    

CI/CD Integration

Example GitHub Actions workflow (see .github/workflows/nix-build.yml):

name: Nix Build

on: [push, pull_request]

permissions:
  contents: read

jobs:
  build:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest]
    runs-on: ${{ matrix.os }}
    steps:
    - uses: actions/checkout@v4
    - uses: DeterminateSystems/nix-installer-action@main
    - uses: DeterminateSystems/magic-nix-cache-action@main
    - run: nix flake check
    - run: nix build --print-build-logs

This uses:

  • Determinate Nix Installer - Fast, reliable Nix installation with flakes enabled by default
  • Magic Nix Cache - Free caching using GitHub Actions cache (no account needed)

Resources