-
Notifications
You must be signed in to change notification settings - Fork 9
Deployment
This guide covers installing, configuring, and maintaining MiBee NVR in production.
The install script downloads the latest release binary, creates the nvr system user, initializes the config, and installs the systemd service — all in one step.
# Install latest version
curl -fsSL https://raw.githubusercontent.com/Mi-Bee-Studio/MiBeeNvr/main/install.sh | sudo bashInstall a specific version:
sudo ./install.sh --version v0.2.0Uninstall (preserves recordings in /var/lib/mibee-nvr):
sudo ./install.sh --uninstallThe installer will prompt for an admin password if no config file exists. After installation, the Web UI is available at http://<host-ip>:9090.
- Docker Engine 20.10+ and Docker Compose v2 (or Podman equivalent)
- Check versions:
docker --version docker compose version
docker run -d
--name mibee-nvr
--restart unless-stopped
-p 9090:9090
-v ./data:/data
ghcr.io/mi-bee-studio/mibeenvr:latest
docker run -d
--name mibee-nvr
--restart unless-stopped
-p 9090:9090
-e NVR_PASSWORD=yourpassword
-v ./data:/data
ghcr.io/mi-bee-studio/mibeenvr:latest
mkdir -p data docker compose up -d
First-time setup: When started without a config file, MiBee NVR auto-generates a default configuration and runs in setup mode — all API endpoints are accessible without authentication. Set a password via the Web UI Settings page or the
NVR_PASSWORDenvironment variable. Once a password is set, authentication is enforced.
-
Auto-initialization: If no config file exists at
/data/mibee-nvr.yaml, one is generated automatically with sensible defaults. No manual setup required. -
Initial password: Set via
NVR_PASSWORDenvironment variable. If not set, the app starts in setup mode (no auth) — set a password through the Web UI Settings page. -
Data directory:
storage.root_diris automatically set to/datainside Docker containers via theNVR_DATA_DIRenvironment variable.
Full configuration with annotated fields:
services:
mibee-nvr:
# Docker image — official pre-built image
image: ghcr.io/mi-bee-studio/mibeenvr:latest
# Container name (for easier management and log viewing)
container_name: mibee-nvr
# Auto-restart policy: always restart unless manually stopped
restart: unless-stopped
# Port mapping: host_port:container_port
ports:
- "9090:9090" # Web UI and REST API
- "2121:2121" # FTP server
- "2122-2140:2122-2140" # FTP passive mode ports
# Volume mount: map host ./data to container /data
# Persists config, recordings, and database
volumes:
- ./data:/data
# Environment variables
environment:
- NVR_DATA_DIR=/data # Data directory path
- TZ=Asia/Shanghai # Timezone
# Health check: verifies service status every 30 seconds
healthcheck:
test: ["CMD", "mibee-nvr", "health"] # Health check command
interval: 30s # Check interval
timeout: 5s # Timeout
start_period: 10s # Grace period after start
retries: 3 # Retry countOption A: Use pre-built image (recommended)
- Image:
ghcr.io/mi-bee-studio/mibeenvr:latest - Architecture tags:
latest (multi-arch: amd64 + arm64)
No extra steps needed — the docker-compose.yml uses the pre-built image by default.
Option B: Build locally
If you need custom builds or want the latest source code:
# Multi-stage build (compiles frontend + backend inside container, requires network)
docker build -t mibee-nvr .
# Cross-compile ARM64 (on host, no QEMU needed)
make docker-build-arm64
# Build both architectures
make docker-build-allAfter building locally, replace the image: field in docker-compose.yml with your local tag.
# View logs (follow mode)
docker compose logs -f mibee-nvr
# View recent logs (last 100 lines)
docker compose logs --tail 100 mibee-nvr
# Restart container
docker compose restart mibee-nvr
# Stop container (preserves data)
docker compose down
# Stop and remove volumes (WARNING: deletes all data!)
docker compose down -v
# Update to latest image
docker compose pull
docker compose up -d
# Container status
docker compose ps
# Resource usage
docker stats mibee-nvr
# Health check status
docker inspect --format='{{.State.Health.Status}}' mibee-nvrNote: The container uses a distroless/scratch base image, so
docker execshell access is not available. Usedocker compose logsfor debugging.
If you prefer not to use Docker Compose, you can run the container directly:
# 1. Login to GHCR (required for private images)
echo YOUR_GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin
# 2. Pull the image
docker pull ghcr.io/mi-bee-studio/mibeenvr:latest
# 3. Run the container
docker run -d \
--name mibee-nvr \
--restart unless-stopped \
-p 9090:9090 \
-p 2121:2121 \
-p 2122-2140:2122-2140 \
-v ./data:/data \
-e NVR_DATA_DIR=/data \
-e TZ=Asia/Shanghai \
ghcr.io/mi-bee-studio/mibeenvr:latest
# 4. Check status
docker ps
docker logs -f mibee-nvr
docker inspect --format='{{.State.Health.Status}}' mibee-nvrRun a specific version:
docker pull ghcr.io/mi-bee-studio/mibeenvr:v0.2.0
docker run -d --name mibee-nvr ... ghcr.io/mi-bee-studio/mibeenvr:v0.2.0Stop and remove:
docker stop mibee-nvr
docker rm mibee-nvrUpdate to latest:
docker stop mibee-nvr
docker rm mibee-nvr
docker pull ghcr.io/mi-bee-studio/mibeenvr:latest
docker run -d ... ghcr.io/mi-bee-studio/mibeenvr:latestBackup:
# 1. Stop container
docker compose stop
# 2. Backup data directory
tar czf nvr-backup-$(date +%Y%m%d).tar.gz data/
# 3. Restart
docker compose startRestore:
# 1. Stop and remove container
docker compose down
# 2. Extract backup
tar xzf nvr-backup-20240101.tar.gz
# 3. Start with restored data
docker compose up -dRaspberry Pi requires the ARM64 image:
# docker-compose.yml — Raspberry Pi configuration
services:
mibee-nvr:
image: ghcr.io/mi-bee-studio/mibeenvr:latest
deploy:
resources:
limits:
memory: 512m # Prevent OOM on RPi 3BImportant notes:
- Segment duration must stay at 30s (
segment_duration: "30s") - Use an external USB disk (ext4) for recording storage
- Limit concurrent recording to 2-3 cameras depending on resolution and bitrate
Permission errors
The container runs as nonroot (UID 65534). Fix mount permission issues:
chown -R 65534:65534 ./dataPort conflicts
Change the left-side (host) port in docker-compose.yml:
ports:
- "8090:9090" # Change host port to 8090Container keeps restarting
Usually a config file error. Check logs:
docker compose logs mibee-nvrFTP won't connect
Ensure passive port range (2122-2140) is mapped and not blocked by firewall.
Wrong timezone
Add the TZ environment variable to docker-compose.yml:
environment:
- TZ=America/New_YorkDocker Compose v1 vs v2
- Use
docker compose(with space, v2) - Not
docker-compose(with hyphen, v1, deprecated)
ONVIF device discovery doesn't work in Docker
ONVIF auto-discovery uses WS-Discovery (UDP multicast to 239.255.255.250:3702). Docker's default bridge network blocks multicast traffic, so auto-discovery won't find devices.
Solutions:
-
Host networking (recommended for discovery): Uncomment
network_mode: hostindocker-compose.ymland remove theportssection. The container shares the host's network stack, enabling multicast. -
Manual probe (works in any network mode): In the Web UI camera page, use the "Manual Probe" section to enter a device IP address directly. This bypasses multicast and works in any Docker configuration.
-
Manual camera addition: Add cameras by specifying the ONVIF endpoint URL directly (e.g.,
http://192.168.1.100/onvif/device_service) in the camera form. ONVIF connection, PTZ control, and streaming work normally in bridge mode — only auto-discovery is affected.
If you prefer full control or the install script doesn't cover your use case:
# 1. Download binary from GitHub Releases
# https://github.com/Mi-Bee-Studio/MiBeeNvr/releases
sudo cp mibee-nvr /usr/local/bin/mibee-nvr
sudo chmod +x /usr/local/bin/mibee-nvr
# 2. Create system user and data directory
sudo useradd -r -s /bin/false -d /var/lib/mibee-nvr nvr
sudo mkdir -p /var/lib/mibee-nvr
sudo chown -R nvr:nvr /var/lib/mibee-nvr
# 3. Initialize config (prompts for admin password)
sudo -u nvr /usr/local/bin/mibee-nvr init \
--password <your-password> \
--data-dir /var/lib/mibee-nvr \
--config /var/lib/mibee-nvr/mibee-nvr.yaml \
--listen ":9090"
# 4. Install systemd service
sudo cp deploy/mibee-nvr.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now mibee-nvrgit clone https://github.com/Mi-Bee-Studio/MiBeeNvr.git
cd MiBeeNvr
# Build for current architecture
make build
# Cross-compile for ARM64 (e.g., Raspberry Pi)
make cross
# Run tests
make test
# Lint
make lintTo deploy a cross-compiled binary directly to a Raspberry Pi:
make deploy RPi_HOST=user@your-rpi-host
make deploy-check RPi_HOST=user@your-rpi-host
make rollback RPi_HOST=user@your-rpi-hostThe service file is maintained in deploy/mibee-nvr.service. Key details:
-
Binary:
/usr/local/bin/mibee-nvr -
Config:
/var/lib/mibee-nvr/mibee-nvr.yaml -
Working directory:
/var/lib/mibee-nvr -
Runs as:
nvruser -
Security:
NoNewPrivileges,PrivateTmp,ProtectSystem=strict,ProtectHome -
Memory limit:
MemoryMax=512M(commented out by default; uncomment for RPi 3B)
Common commands:
sudo systemctl start mibee-nvr
sudo systemctl stop mibee-nvr
sudo systemctl restart mibee-nvr
sudo systemctl status mibee-nvr
sudo journalctl -u mibee-nvr -f # follow logsCaddy provides automatic HTTPS with minimal configuration:
nvr.example.com {
reverse_proxy localhost:9090
}For TLS with explicit email:
{
email admin@example.com
}
nvr.example.com {
reverse_proxy localhost:9090
}server {
listen 80;
server_name nvr.example.com;
location / {
proxy_pass http://localhost:9090;
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;
}
location /dav/ {
proxy_pass http://localhost:9090;
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_request_buffering off;
proxy_buffering off;
}
}The Raspberry Pi 3B has 905MB RAM. For stable operation:
-
Segment duration: Use 30s (
segment_duration: "30s"). Longer durations hold more frames in RAM (e.g., 120s = 60-80MB per segment). -
Memory limit: Uncomment
MemoryMax=512Mindeploy/mibee-nvr.serviceto prevent OOM kills. - Storage: Use an external USB disk (ext4) for recordings. The SD card will wear out quickly with continuous writes.
- Cameras: Limit to 2-3 concurrent H.264/H.265 streams depending on resolution and bitrate.
sudo ./install.sh --version v0.2.0The script stops the service, replaces the binary, and restarts automatically. Config and recordings are preserved.
sudo systemctl stop mibee-nvr
sudo cp mibee-nvr /usr/local/bin/mibee-nvr
sudo chmod +x /usr/local/bin/mibee-nvr
sudo systemctl start mibee-nvrAlways back up your config before updating:
sudo cp /var/lib/mibee-nvr/mibee-nvr.yaml /var/lib/mibee-nvr/mibee-nvr.yaml.backupsudo journalctl -u mibee-nvr -n 100 # last 100 lines
sudo journalctl -u mibee-nvr -f # follow
sudo journalctl -u mibee-nvr --since "1 hour ago"sudo systemctl is-active mibee-nvr
curl -f http://localhost:9090/api/healthdf -h /var/lib/mibee-nvr
du -sh /var/lib/mibee-nvr/recordingsMetrics are available at /metrics (public, no auth required):
curl http://localhost:9090/metricssudo journalctl -u mibee-nvr -n 50
# Verify config syntax
sudo -u nvr /usr/local/bin/mibee-nvr -config /var/lib/mibee-nvr/mibee-nvr.yaml# Test RTSP connection
ffmpeg -rtsp_transport tcp -i "rtsp://admin:pass@192.168.1.100:554/stream" -t 5 -f null -
# Check network
ping 192.168.1.100sudo lsof -i :9090
sudo lsof -i :2121ls -la /var/lib/mibee-nvr/
sudo -u nvr ls /var/lib/mibee-nvr/Reduce segment_duration to 30s. On RPi 3B, uncomment MemoryMax=512M in the service file.
GitHub | Report Issue | MIT License
Setup & Basics
Camera & Streaming
Integrations
Advanced
安装配置
摄像头与流媒体
集成
进阶