Skip to main content

Raspberry Pi

This guide covers deploying Nekzus on Raspberry Pi devices. The Raspberry Pi makes an excellent low-power home server for running Nekzus as your local network gateway.


Requirements​

Hardware​

ComponentMinimumRecommended
ModelRaspberry Pi 3B+ (arm64)Raspberry Pi 4 (4GB+) or Pi 5
RAM1 GB4 GB+
Storage8 GB SD card32 GB+ SSD via USB 3.0
NetworkEthernet or Wi-FiGigabit Ethernet
Architecture Note

Nekzus requires 64-bit ARM (arm64/aarch64). Raspberry Pi 3B+ and later support arm64 with the appropriate OS. Older 32-bit (armv7) Raspberry Pi models are not supported.

Operating System​

Supported 64-bit operating systems:

OSVersionRecommended
Raspberry Pi OS64-bit (Bookworm)Yes
Ubuntu Server22.04 LTS or 24.04 LTSYes
Debian12 (Bookworm)Yes
DietPi64-bitYes

Software Dependencies​

  • Docker 24.0+ (arm64 build)
  • Docker Compose v2.20+
  • Network access to Docker Hub

Pre-Installation Setup​

Flash the OS​

  1. Download Raspberry Pi Imager from raspberrypi.com/software

  2. Select the OS:

    • Choose "Raspberry Pi OS (64-bit)" or "Ubuntu Server 24.04 LTS (64-bit)"
  3. Configure settings (click the gear icon):

    • Set hostname (e.g., nekzus)
    • Enable SSH
    • Set username and password
    • Configure Wi-Fi (if not using Ethernet)
    • Set locale and timezone
  4. Flash to SD card or SSD

Initial Boot​

After booting, connect via SSH:

ssh pi@nekzus.local
# Or use the IP address
ssh pi@192.168.1.100

Update the system:

sudo apt update && sudo apt upgrade -y
sudo reboot

Installation Methods​

Docker is the recommended installation method for Raspberry Pi deployments.

Install Docker​

# Install Docker using the convenience script
curl -fsSL https://get.docker.com | sh

# Add your user to the docker group
sudo usermod -aG docker $USER

# Apply group changes (or log out and back in)
newgrp docker

# Verify installation
docker --version
docker compose version

Deploy Nekzus​

Create a project directory:

mkdir -p ~/nekzus && cd ~/nekzus

Create docker-compose.yml:

docker-compose.yml
services:
nekzus:
image: nstalgic/nekzus:latest
container_name: nekzus
environment:
NEKZUS_JWT_SECRET: "${NEKZUS_JWT_SECRET}"
NEKZUS_BOOTSTRAP_TOKEN: "${NEKZUS_BOOTSTRAP_TOKEN}"
NEKZUS_ADDR: ":8080"
ports:
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- nekzus-data:/app/data
restart: unless-stopped
healthcheck:
test: ["/app/nekzus", "--health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
deploy:
resources:
limits:
memory: 512M

volumes:
nekzus-data:

Create the .env file with secure secrets:

echo "NEKZUS_JWT_SECRET=$(openssl rand -base64 32)" > .env
echo "NEKZUS_BOOTSTRAP_TOKEN=$(openssl rand -base64 24)" >> .env
chmod 600 .env

Start the service:

docker compose up -d

Verify the deployment:

# Check container status
docker compose ps

# View logs
docker compose logs -f nekzus

# Test health endpoint
curl http://localhost:8080/healthz

Building from Source​

Building from source is useful for development or when you need custom builds.

Install Dependencies​

# Update package list
sudo apt update

# Install build tools
sudo apt install -y gcc libc6-dev make git wget

# Install Go 1.25
wget https://go.dev/dl/go1.25.linux-arm64.tar.gz
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf go1.25.linux-arm64.tar.gz
rm go1.25.linux-arm64.tar.gz

# Add Go to PATH
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
echo 'export PATH=$PATH:$HOME/go/bin' >> ~/.bashrc
source ~/.bashrc

# Verify Go installation
go version

# Install Node.js 20 (for web UI build)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs

# Verify Node.js
node --version
npm --version

Build Nekzus​

# Clone the repository
git clone https://github.com/nstalgic/nekzus.git
cd nekzus

# Build web UI and Go binary
make build-all

# Verify the build
./bin/nekzus --version
Build Time

Building on a Raspberry Pi 4 takes approximately 3-5 minutes. On a Raspberry Pi 3B+, expect 8-12 minutes.

Run Nekzus​

# Generate secrets
export NEKZUS_JWT_SECRET=$(openssl rand -base64 32)
export NEKZUS_BOOTSTRAP_TOKEN=$(openssl rand -base64 24)

# Run without TLS (development)
./bin/nekzus --insecure-http

# Run with custom config
./bin/nekzus --config configs/config.yaml --insecure-http

Performance Optimization​

Memory Management​

Raspberry Pi devices have limited RAM. Apply these optimizations:

Docker Memory Limits​

Set memory limits in your docker-compose.yml:

deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256M
ModelRecommended LimitNotes
Pi 3B+ (1GB)384MLeaves room for OS
Pi 4 (2GB)512MComfortable margin
Pi 4 (4GB+)1GRoom for toolbox services
Pi 5 (8GB)2GFull feature support

Reduce Container Overhead​

Disable features you do not need:

environment:
# Disable Kubernetes discovery if not using K8s
NEKZUS_DISCOVERY_K8S_ENABLED: "false"
# Reduce log verbosity
NEKZUS_LOG_LEVEL: "warn"

Storage Configuration​

SD cards are slow and wear out quickly. SSD storage dramatically improves performance and reliability.

  1. Prepare the SSD:

    # List connected drives
    lsblk

    # Format the SSD (replace sdX with your device)
    sudo mkfs.ext4 /dev/sdX1
  2. Enable USB boot (Pi 4/5 only):

    # Update bootloader for USB boot support
    sudo rpi-eeprom-update -d -a
    sudo reboot
  3. Flash OS to SSD using Raspberry Pi Imager

  4. Boot from SSD by removing the SD card

Docker Data on SSD​

If using SD card for OS but SSD for data:

# Mount SSD
sudo mkdir -p /mnt/ssd
sudo mount /dev/sda1 /mnt/ssd

# Add to fstab for persistence
echo '/dev/sda1 /mnt/ssd ext4 defaults,noatime 0 2' | sudo tee -a /etc/fstab

# Configure Docker to use SSD
sudo mkdir -p /etc/docker
cat <<EOF | sudo tee /etc/docker/daemon.json
{
"data-root": "/mnt/ssd/docker"
}
EOF

# Restart Docker
sudo systemctl restart docker

Swap Configuration​

Configure swap for memory-constrained devices:

Increase Swap Size​

# Disable current swap
sudo dphys-swapfile swapoff

# Edit swap configuration
sudo nano /etc/dphys-swapfile

Set these values:

/etc/dphys-swapfile
CONF_SWAPSIZE=2048
CONF_SWAPFACTOR=2
CONF_MAXSWAP=4096

Apply changes:

sudo dphys-swapfile setup
sudo dphys-swapfile swapon

# Verify
free -h

Place swap on SSD for better performance:

# Create swap file on SSD
sudo fallocate -l 2G /mnt/ssd/swapfile
sudo chmod 600 /mnt/ssd/swapfile
sudo mkswap /mnt/ssd/swapfile
sudo swapon /mnt/ssd/swapfile

# Make permanent
echo '/mnt/ssd/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

CPU Governor​

Set the CPU governor for consistent performance:

# Check current governor
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

# Set to performance mode
echo 'performance' | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor

# Make permanent
echo 'GOVERNOR="performance"' | sudo tee /etc/default/cpufrequtils
sudo systemctl restart cpufrequtils

Network Setup​

Static IP Configuration​

A static IP ensures consistent access to Nekzus.

Edit /etc/dhcpcd.conf:

sudo nano /etc/dhcpcd.conf

Add at the end:

/etc/dhcpcd.conf
interface eth0
static ip_address=192.168.1.100/24
static routers=192.168.1.1
static domain_name_servers=192.168.1.1 8.8.8.8

Apply changes:

sudo systemctl restart dhcpcd

mDNS/Avahi for Discovery​

Enable mDNS for local network discovery:

# Install Avahi
sudo apt install -y avahi-daemon avahi-utils

# Enable and start the service
sudo systemctl enable avahi-daemon
sudo systemctl start avahi-daemon

# Verify mDNS is working
avahi-browse -alr

Your Raspberry Pi will be accessible at nekzus.local (using your configured hostname).

Create a service file for Nekzus discovery:

sudo nano /etc/avahi/services/nekzus.service
/etc/avahi/services/nekzus.service
<?xml version="1.0" standalone='no'?>
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
<service-group>
<name replace-wildcards="yes">Nekzus on %h</name>
<service>
<type>_http._tcp</type>
<port>8080</port>
<txt-record>path=/</txt-record>
<txt-record>type=nekzus</txt-record>
</service>
</service-group>

Reload Avahi:

sudo systemctl reload avahi-daemon

Firewall Configuration​

Configure UFW (Uncomplicated Firewall) for security:

# Install UFW
sudo apt install -y ufw

# Set default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow SSH (important - do this first!)
sudo ufw allow ssh

# Allow Nekzus web interface
sudo ufw allow 8080/tcp comment 'Nekzus HTTP'

# Allow HTTPS if using TLS
sudo ufw allow 8443/tcp comment 'Nekzus HTTPS'

# Allow mDNS for discovery
sudo ufw allow 5353/udp comment 'mDNS'

# Enable firewall
sudo ufw enable

# Check status
sudo ufw status verbose

Running as a Service​

systemd Service Setup​

Create a systemd service for automatic startup and management.

For Docker Deployments​

Docker Compose with restart: unless-stopped handles this automatically. For additional control:

sudo nano /etc/systemd/system/nekzus.service
/etc/systemd/system/nekzus.service
[Unit]
Description=Nekzus API Gateway
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/home/pi/nekzus
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
ExecReload=/usr/bin/docker compose restart
TimeoutStartSec=120
TimeoutStopSec=60

[Install]
WantedBy=multi-user.target

For Binary Deployments​

sudo nano /etc/systemd/system/nekzus.service
/etc/systemd/system/nekzus.service
[Unit]
Description=Nekzus API Gateway
Documentation=https://github.com/nstalgic/nekzus
After=network-online.target docker.socket
Wants=network-online.target

[Service]
Type=simple
User=pi
Group=pi
WorkingDirectory=/home/pi/nekzus

# Environment variables
Environment="NEKZUS_ADDR=:8080"
EnvironmentFile=/home/pi/nekzus/.env

# Run command
ExecStart=/home/pi/nekzus/bin/nekzus --insecure-http
ExecReload=/bin/kill -HUP $MAINPID

# Restart policy
Restart=always
RestartSec=10
StartLimitInterval=60
StartLimitBurst=3

# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/home/pi/nekzus/data
PrivateTmp=true

# Resource limits
MemoryMax=512M
TasksMax=100

[Install]
WantedBy=multi-user.target

Enable and Start the Service​

# Reload systemd
sudo systemctl daemon-reload

# Enable service to start on boot
sudo systemctl enable nekzus

# Start the service
sudo systemctl start nekzus

# Check status
sudo systemctl status nekzus

Service Management Commands​

# Start service
sudo systemctl start nekzus

# Stop service
sudo systemctl stop nekzus

# Restart service
sudo systemctl restart nekzus

# View logs
sudo journalctl -u nekzus -f

# View logs since boot
sudo journalctl -u nekzus -b

Monitoring​

Resource Usage​

Monitor system resources on your Raspberry Pi:

htop (Interactive)​

sudo apt install -y htop
htop

Command-Line Monitoring​

# CPU and memory usage
vmstat 1 5

# Docker container stats
docker stats --no-stream

# Disk usage
df -h

# Memory details
free -h

# Temperature (important for Pi!)
vcgencmd measure_temp

Prometheus Metrics​

Nekzus exposes Prometheus metrics at /metrics. Set up monitoring:

docker-compose.yml (with monitoring)
services:
nekzus:
# ... existing configuration ...

prometheus:
image: prom/prometheus:latest
container_name: prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
ports:
- "9090:9090"
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.retention.time=7d'
restart: unless-stopped
deploy:
resources:
limits:
memory: 256M

volumes:
nekzus-data:
prometheus-data:
prometheus.yml
global:
scrape_interval: 30s
evaluation_interval: 30s

scrape_configs:
- job_name: 'nekzus'
static_configs:
- targets: ['nekzus:8080']
metrics_path: /metrics

Log Management​

View Logs​

# Docker logs
docker compose logs -f nekzus

# With timestamps
docker compose logs -f --timestamps nekzus

# Last 100 lines
docker compose logs --tail 100 nekzus

# systemd logs
sudo journalctl -u nekzus -f

Log Rotation​

Docker handles log rotation automatically. Configure limits:

docker-compose.yml
services:
nekzus:
# ... existing configuration ...
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"

Temperature Monitoring​

Raspberry Pi can throttle under high temperatures. Monitor and alert:

# Check temperature
vcgencmd measure_temp

# Check for throttling
vcgencmd get_throttled

Throttling codes:

BitMeaning
0Under-voltage detected
1Arm frequency capped
2Currently throttled
3Soft temperature limit active
16Under-voltage has occurred
17Arm frequency capped has occurred
18Throttling has occurred
19Soft temperature limit has occurred
Cooling Recommendation

Use a heatsink and fan for sustained workloads. Active cooling prevents thermal throttling and extends SD card lifespan.


Troubleshooting​

ARM-Specific Issues​

Container fails to start with exec format error

This error indicates an architecture mismatch. Ensure you are using arm64 images:

# Check system architecture
uname -m
# Should output: aarch64

# Verify Docker platform
docker info | grep Architecture
# Should output: aarch64

# Pull arm64 image explicitly
docker pull --platform linux/arm64 nstalgic/nekzus:latest

If running a 32-bit OS, upgrade to 64-bit Raspberry Pi OS or Ubuntu Server.

Build fails with CGO errors

SQLite requires CGO. Install the necessary dependencies:

sudo apt install -y gcc libc6-dev libsqlite3-dev

# Verify GCC is working
gcc --version

# Build with CGO enabled
CGO_ENABLED=1 go build -o bin/nekzus ./cmd/nekzus
Out of memory during build

Increase swap space temporarily:

# Create temporary swap
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

# Build
make build-all

# Remove temporary swap
sudo swapoff /swapfile
sudo rm /swapfile

Alternatively, cross-compile on a more powerful machine.

Performance Issues​

Slow response times
  1. Check if throttling:

    vcgencmd get_throttled
  2. Check CPU/memory usage:

    docker stats --no-stream
  3. Switch to SSD storage - SD cards have high latency

  4. Reduce memory limits if swapping excessively:

    free -h
High CPU usage at idle
  1. Check discovery poll intervals - increase them:

    discovery:
    docker:
    poll_interval: "60s" # Increase from default
    mdns:
    scan_interval: "120s"
  2. Disable unused discovery providers:

    discovery:
    kubernetes:
    enabled: false
Container keeps restarting

Check logs for the cause:

docker compose logs nekzus --tail 50

# Check container exit code
docker inspect nekzus --format='{{.State.ExitCode}}'

Common causes:

  • Out of memory (exit code 137)
  • Configuration errors (exit code 1)
  • Missing environment variables

Network Issues​

Cannot access from other devices
  1. Check firewall:

    sudo ufw status
    sudo ufw allow 8080/tcp
  2. Verify the service is listening:

    ss -tlnp | grep 8080
  3. Test locally first:

    curl http://localhost:8080/healthz
  4. Check Pi IP address:

    hostname -I
mDNS discovery not working
  1. Verify Avahi is running:

    sudo systemctl status avahi-daemon
  2. Check mDNS port is open:

    sudo ufw allow 5353/udp
  3. Test mDNS resolution:

    avahi-resolve -n nekzus.local
Docker socket permission denied
# Add user to docker group
sudo usermod -aG docker $USER

# Log out and back in, or run:
newgrp docker

# Verify
docker ps

Storage Issues​

SD card is full
  1. Check disk usage:

    df -h
  2. Clean Docker resources:

    docker system prune -a --volumes
  3. Remove old logs:

    sudo journalctl --vacuum-time=7d
Slow SD card performance

Check SD card speed class. Use at least A1/Class 10 cards, or preferably switch to USB SSD.

# Test write speed
dd if=/dev/zero of=/tmp/test bs=1M count=256 conv=fsync

# Test read speed
dd if=/tmp/test of=/dev/null bs=1M

# Clean up
rm /tmp/test

Expected speeds:

  • Class 10 SD: 10-20 MB/s
  • A1 SD: 30-50 MB/s
  • USB 3.0 SSD: 200-400 MB/s

Maintenance​

Updates​

Docker Updates​

cd ~/nekzus

# Pull latest image
docker compose pull

# Restart with new image
docker compose up -d

# Remove old images
docker image prune -f

Binary Updates​

cd ~/nekzus

# Stop service
sudo systemctl stop nekzus

# Pull latest code
git pull origin main

# Rebuild
make build-all

# Start service
sudo systemctl start nekzus

Backup​

Backup Data Volume​

# Stop the service
docker compose stop nekzus

# Backup volume
docker run --rm -v nekzus-data:/data -v $(pwd):/backup alpine \
tar czf /backup/nekzus-backup-$(date +%Y%m%d).tar.gz -C /data .

# Restart
docker compose start nekzus

Restore from Backup​

# Stop the service
docker compose stop nekzus

# Restore volume
docker run --rm -v nekzus-data:/data -v $(pwd):/backup alpine \
tar xzf /backup/nekzus-backup-20240101.tar.gz -C /data

# Restart
docker compose start nekzus

Next Steps​