Skip to content
Niuniu

Self-Hosted

Docker Compose and bare-metal deployment paths, PostgreSQL, Caddy reverse proxy, systemd, and first-admin setup.

Self-Hosted is the right choice for teams that need on-premises deployment, compliance, or an air-gapped environment. It runs on PostgreSQL and supports any number of users.

Two deployment shapes are supported — pick whichever fits:

MethodBest forProsDifficulty
A. Docker ComposeStandard production, on-prem serversOne command brings up the full stack (DB + server + Caddy); upgrades and rollbacks are simple★☆☆
B. Bare-metalExisting PostgreSQL, fine-grained system integration, air-gapped / no-Docker hosts (Kylin, UOS, …)Lower resource footprint, integrates with existing systemd / Nginx / internal CAs★★☆

Both methods run the same application: one niuniu-server process + a PostgreSQL database + a reverse proxy. The difference is only in packaging.

Common prerequisites

  • Linux server (Ubuntu 22.04+ / Debian 12+ / RHEL 9+ / Alibaba Linux 3, etc.)
  • At least 2 vCPU / 4 GB RAM / 20 GB disk
  • A domain name pointed at the server (for HTTPS)
  • Outbound network access to the Anthropic API (or your chosen third-party endpoint)

Method A: Docker Compose deployment

A.1 Prerequisites

  • Docker 24+ and Docker Compose v2
# Ubuntu / Debian one-liner
curl -fsSL https://get.docker.com | sh
sudo systemctl enable --now docker

A.2 Download the deployment template

git clone https://github.com/niuniu-dev/niuniu.git
cd niuniu/relay/deploy
cp server.yaml.example server.yaml

Edit server.yaml and replace the two placeholders:

server:
  host: 0.0.0.0
  port: 3000

storage:
  driver: postgres
  postgres:
    dsn: "postgres://niuniu:your-strong-password@postgres:5432/niuniu_server?sslmode=disable"

auth:
  enabled: true
  token_expiry: 15m
  refresh_expiry: 168h
  users:
    - username: admin
      password: your-admin-password
      display_name: Admin
      role: admin

agent:
  default: claude-code
  claude_code:
    command: claude
    args: []

workspace:
  base_dir: /data/.niuniu/workspaces

harness:
  enabled: true

Inside the container HOME=/data, so workspaces default to /data/.niuniu/workspaces, persisted via the server-data volume.

A.3 Configure environment variables

Create .env under relay/deploy/:

POSTGRES_PASSWORD=match the strong password used in server.yaml DSN
TEAM_DOMAIN=team.example.com
RELAY_DOMAIN=relay.example.com   # leave blank if not deploying the relay
DEMO_DOMAIN=                     # usually blank
WEBSITE_DOMAIN=                  # usually blank
WEBSITE_APEX_DOMAIN=

A.4 Start the stack

docker compose up -d postgres niuniu-server caddy

The first start initializes PostgreSQL (creates the niuniu_server database) and the server schema automatically.

A.5 Caddy reverse proxy

Caddyfile (sample shipped in the repo):

{$TEAM_DOMAIN} {
    reverse_proxy niuniu-server:3000
}

Caddy will request an HTTPS certificate from Let’s Encrypt. If you use an internal CA, add tls /path/to/cert.pem /path/to/key.pem to the site block.

A.6 Verify

curl -I https://team.example.com/api/health
# Expected: HTTP/2 200

Open https://team.example.com in a browser and log in with admin and the password you set.


Method B: Bare-metal deployment

B.1 Install runtime dependencies

niuniu-server spawns git, node, python3, and claude as child processes for agent workflows, so install them up front:

# Debian / Ubuntu
sudo apt update
sudo apt install -y git curl ca-certificates python3 postgresql

# Node.js 20 LTS
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs

# Claude Code and Codex CLI (either one or both)
sudo npm install -g @anthropic-ai/claude-code @openai/codex

On RHEL / Kylin / UOS, swap apt for dnf / yum; package names are identical.

B.2 Create the PostgreSQL database

sudo -u postgres psql <<'SQL'
CREATE USER niuniu WITH PASSWORD 'your-strong-password';
CREATE DATABASE niuniu_server OWNER niuniu;
GRANT ALL PRIVILEGES ON DATABASE niuniu_server TO niuniu;
SQL

If the database lives on a different host, allow the server’s subnet in pg_hba.conf and update the DSN below accordingly.

B.3 Get the niuniu-server binary

Option 1: download a pre-built release (recommended)

Grab niuniu-server-<version>-linux-<arch> from GitHub Releases, rename to niuniu-server, and install it:

sudo install -m 0755 niuniu-server-*-linux-amd64 /usr/local/bin/niuniu-server

Option 2: build from source

Requires Go 1.25+ and pnpm 9+:

git clone https://github.com/niuniu-dev/niuniu.git
cd niuniu
make build-linux
sudo install -m 0755 bin/niuniu-server-*-linux-amd64 /usr/local/bin/niuniu-server

B.4 Create the system user and directories

sudo useradd --system --home /var/lib/niuniu --create-home --shell /usr/sbin/nologin niuniu
sudo mkdir -p /etc/niuniu /var/lib/niuniu/.niuniu/workspaces
sudo chown -R niuniu:niuniu /var/lib/niuniu

B.5 Write the config

/etc/niuniu/config.yaml:

server:
  host: 0.0.0.0
  port: 3000

storage:
  driver: postgres
  postgres:
    dsn: "postgres://niuniu:your-strong-password@localhost:5432/niuniu_server?sslmode=disable"

auth:
  enabled: true
  token_expiry: 15m
  refresh_expiry: 168h
  users:
    - username: admin
      password: your-admin-password
      display_name: Admin
      role: admin

agent:
  default: claude-code
  claude_code:
    command: claude
    args: []

workspace:
  base_dir: /var/lib/niuniu/.niuniu/workspaces

harness:
  enabled: true
sudo chown niuniu:niuniu /etc/niuniu/config.yaml
sudo chmod 0640 /etc/niuniu/config.yaml

B.6 systemd service unit

/etc/systemd/system/niuniu-server.service:

[Unit]
Description=Niuniu Team Server
Documentation=https://github.com/niuniu-dev/niuniu
After=network-online.target postgresql.service
Wants=network-online.target

[Service]
Type=simple
User=niuniu
Group=niuniu
Environment=HOME=/var/lib/niuniu
ExecStart=/usr/local/bin/niuniu-server --config /etc/niuniu/config.yaml
Restart=on-failure
RestartSec=5s
WorkingDirectory=/var/lib/niuniu
LimitNOFILE=65536
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/var/lib/niuniu
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target

Start it:

sudo systemctl daemon-reload
sudo systemctl enable --now niuniu-server
sudo systemctl status niuniu-server
journalctl -u niuniu-server -f      # live logs

Note the Environment=HOME=/var/lib/niuniu line — the claude CLI stores OAuth credentials under $HOME/.claude/, so HOME must match the systemd user’s real home directory, otherwise the agent will fail to log in to the model.

B.7 Reverse proxy (Caddy example)

sudo apt install -y caddy

/etc/caddy/Caddyfile:

team.example.com {
    reverse_proxy localhost:3000
}
sudo systemctl reload caddy

Nginx users can use the following equivalent — note that WebSocket / SSE require proxy_http_version 1.1 plus the Upgrade header:

server {
    listen 443 ssl http2;
    server_name team.example.com;

    ssl_certificate     /etc/letsencrypt/live/team.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/team.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
}

B.8 Verify

curl -I https://team.example.com/api/health
# Expected: HTTP/2 200

Open https://team.example.com in a browser and log in with admin and the password you set.


First admin

Both methods use the admin account from auth.users in server.yaml / config.yaml for first login. Once logged in, immediately:

  1. Go to Settings → Users and change the admin password to a new value — then write the new value back into the config file so the plaintext password doesn’t linger
  2. Create regular member accounts; keep admin for operations only, not daily development
  3. (Optional) enable MFA from Settings → Users → Security (TOTP)

Backups

PostgreSQL is the critical asset — schedule a weekly pg_dump:

# Docker Compose
docker compose exec postgres pg_dump -U niuniu niuniu_server | gzip > backup-$(date +%F).sql.gz

# Bare metal
sudo -u postgres pg_dump niuniu_server | gzip > backup-$(date +%F).sql.gz

Also include the workspace tree (Docker: the server-data volume / bare metal: /var/lib/niuniu/.niuniu/) in the same incremental backup. Retain 4–8 weeks.

Upgrading

Docker Compose:

cd niuniu/relay/deploy
git pull
docker compose pull
docker compose up -d --force-recreate niuniu-server

Bare metal:

# Download the new binary (or rerun make build-linux)
sudo systemctl stop niuniu-server
sudo install -m 0755 niuniu-server-*-linux-amd64 /usr/local/bin/niuniu-server
sudo systemctl start niuniu-server
journalctl -u niuniu-server -f

Always back up the database before upgrading. Schema migrations run automatically on service start; a failed migration keeps the old schema intact instead of corrupting data.

Next steps

Edit this page on GitHub →