Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -276,9 +276,8 @@ RUN mkdir -p /bin /etc/lightnvr/go2rtc && \
# Create basic configuration file
echo "# go2rtc configuration file" > /etc/lightnvr/go2rtc/go2rtc.yaml && \
echo "api:" >> /etc/lightnvr/go2rtc/go2rtc.yaml && \
echo " listen: :1984" >> /etc/lightnvr/go2rtc/go2rtc.yaml && \
echo " listen: 127.0.0.1:1984" >> /etc/lightnvr/go2rtc/go2rtc.yaml && \
echo " base_path: /go2rtc" >> /etc/lightnvr/go2rtc/go2rtc.yaml && \
echo ' origin: "*"' >> /etc/lightnvr/go2rtc/go2rtc.yaml && \
echo "" >> /etc/lightnvr/go2rtc/go2rtc.yaml && \
echo "webrtc:" >> /etc/lightnvr/go2rtc/go2rtc.yaml && \
echo " ice_servers:" >> /etc/lightnvr/go2rtc/go2rtc.yaml && \
Expand Down Expand Up @@ -415,12 +414,22 @@ RUN chmod +x /usr/local/bin/docker-entrypoint.sh
COPY docker-start.sh /bin/start.sh
RUN chmod +x /bin/start.sh

# Run the recorder and its managed go2rtc child without root privileges.
RUN groupadd --gid 10001 lightnvr && useradd --uid 10001 --gid 10001 --no-create-home --shell /usr/sbin/nologin lightnvr && \
mkdir -p /var/lib/lightnvr/data/recordings /var/lib/lightnvr/data/database \
/var/lib/lightnvr/data/models /var/log/lightnvr /var/run/lightnvr && \
chmod -R go-w /var/lib/lightnvr /var/log/lightnvr /var/run/lightnvr && \
chown -R 10001:10001 /etc/lightnvr /var/lib/lightnvr/data \
/var/log/lightnvr /var/run/lightnvr

USER 10001:10001

# Define volumes for persistent data only
# Note: Do NOT mount /var/lib/lightnvr directly as it will overwrite web assets
VOLUME ["/etc/lightnvr", "/var/lib/lightnvr/data"]
VOLUME ["/etc/lightnvr", "/var/lib/lightnvr/data/database", "/var/lib/lightnvr/data/recordings", "/var/lib/lightnvr/data/models"]

# Expose ports
EXPOSE 8080 8554 8555 8555/udp 1984
EXPOSE 8080 8554 8555 8555/udp

# Environment variables for configuration
ENV GO2RTC_CONFIG_PERSIST=true \
Expand Down
15 changes: 14 additions & 1 deletion Dockerfile.alpine
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,27 @@ COPY --from=builder /lib/libsod.so.1 /lib/libsod.so
# Copy database migrations
COPY --from=builder /opt/db/migrations/ /usr/share/lightnvr/migrations/

# Use a PID location writable by the runtime user.
RUN sed -i 's|/var/run/lightnvr.pid|/var/run/lightnvr/lightnvr.pid|g' /etc/lightnvr/lightnvr.ini

# Expose required ports
EXPOSE 8080

# Run the recorder and its managed go2rtc child without root privileges.
RUN addgroup -g 10001 -S lightnvr && adduser -u 10001 -S -D -H -G lightnvr lightnvr && \
mkdir -p /var/lib/lightnvr/data/recordings /var/lib/lightnvr/data/database \
/var/lib/lightnvr/data/models /var/log/lightnvr /var/run/lightnvr && \
chmod -R go-w /var/lib/lightnvr /var/log/lightnvr /var/run/lightnvr && \
chown -R 10001:10001 /etc/lightnvr /var/lib/lightnvr/data \
/var/log/lightnvr /var/run/lightnvr

USER 10001:10001

# Volume for configuration and data persistence
# /etc/lightnvr - Configuration files
# /var/lib/lightnvr/data - Persistent data (database, recordings, models, etc.)
VOLUME /etc/lightnvr
VOLUME /var/lib/lightnvr/data
VOLUME /var/lib/lightnvr/data/database /var/lib/lightnvr/data/recordings /var/lib/lightnvr/data/models

# Command to start the service
CMD ["/bin/lightnvr", "-c", "/etc/lightnvr/lightnvr.ini"]
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -426,16 +426,20 @@ docker run -d \
-p 8554:8554 \
-p 8555:8555 \
-p 8555:8555/udp \
-p 1984:1984 \
-v ./config:/etc/lightnvr \
-v ./data:/var/lib/lightnvr/data \
--security-opt no-new-privileges --cap-drop ALL \
-v lightnvr-config:/etc/lightnvr \
-v lightnvr-database:/var/lib/lightnvr/data/database \
-v lightnvr-recordings:/var/lib/lightnvr/data/recordings \
-v lightnvr-models:/var/lib/lightnvr/data/models \
-e TZ=America/New_York \
ghcr.io/opensensor/lightnvr:latest
```

#### Volume Mounts Explained

The container uses two volume mounts for persistence:
The example uses named volumes for config, database, recordings, and models. Existing bind mounts must be owned by UID:GID `10001:10001`; see the [migration notes](docs/DOCKER.md#security-migration-non-root-runtime-and-private-go2rtc-api) before upgrading. MSE/WebRTC require the authenticated [reverse-proxy routes](docs/REVERSE_PROXY.md).

Persistent paths:

- **`/etc/lightnvr`** - Configuration files
- `lightnvr.ini` - Main configuration
Expand All @@ -453,7 +457,7 @@ The container uses two volume mounts for persistence:
- **8080** - Web UI (HTTP)
- **8554** - RTSP streaming
- **8555** - WebRTC (TCP/UDP)
- **1984** - go2rtc API
- **1984** - internal go2rtc API, bound to loopback; do not publish

#### Environment Variables

Expand Down
3 changes: 1 addition & 2 deletions config/go2rtc/go2rtc-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@

# Test configuration uses different ports to avoid conflicts with production
api:
listen: :11984
origin: "*"
listen: 127.0.0.1:11984
# Note: base_path removed for simpler API access during testing
# Use base_path: /go2rtc (no trailing slash) when running behind lightNVR's reverse proxy

Expand Down
3 changes: 1 addition & 2 deletions config/go2rtc/go2rtc.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# go2rtc configuration file
api:
listen: :1984
origin: "*"
listen: 127.0.0.1:1984
base_path: /go2rtc

rtsp:
Expand Down
18 changes: 13 additions & 5 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@ services:
- "8554:8554" # RTSP
- "8555:8555" # WebRTC TCP
- "8555:8555/udp" # WebRTC UDP
- "1984:1984" # go2rtc API
volumes:
# Only mount what needs persistence
# DO NOT mount /var/lib/lightnvr directly - it will overwrite web assets!
- ./config:/etc/lightnvr
- ./data:/var/lib/lightnvr/data
# Named config/database volumes avoid exposing broad host directories.
- lightnvr-config:/etc/lightnvr
- lightnvr-database:/var/lib/lightnvr/data/database
- ./data/recordings:/var/lib/lightnvr/data/recordings
- lightnvr-models:/var/lib/lightnvr/data/models
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
environment:
# Timezone configuration
- TZ=UTC # Change to your timezone (e.g., America/New_York, Europe/London)
Expand All @@ -44,3 +48,7 @@ networks:
lightnvr:
driver: bridge

volumes:
lightnvr-config:
lightnvr-database:
lightnvr-models:
60 changes: 10 additions & 50 deletions docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ init_config() {
; LightNVR Configuration File (INI format)

[general]
pid_file = /var/run/lightnvr.pid
pid_file = /var/run/lightnvr/lightnvr.pid
log_file = /var/log/lightnvr/lightnvr.log
log_level = 2 ; 0=ERROR, 1=WARN, 2=INFO, 3=DEBUG
syslog_enabled = false
Expand Down Expand Up @@ -188,7 +188,7 @@ EOF

# Create go2rtc config directory if it doesn't exist
mkdir -p /etc/lightnvr/go2rtc
chown -R "$(id -u):$(id -g)" /etc/lightnvr/go2rtc
# Volume ownership must be prepared by the operator before startup.

# Always regenerate go2rtc.yaml fresh at startup to avoid stale/corrupted
# configs from prior versions causing stream errors (see issue #165).
Expand All @@ -207,8 +207,7 @@ EOF
cat > /etc/lightnvr/go2rtc/go2rtc.yaml << 'EOF'
# go2rtc configuration file
api:
listen: :1984
origin: "*"
listen: 127.0.0.1:1984
base_path: /go2rtc

rtsp:
Expand Down Expand Up @@ -241,48 +240,8 @@ EOF
log_info "No models to copy, skipping model initialization"
fi

# Ensure proper permissions (may fail on NFS, which is okay)
# Skip entirely if the user opts out via environment variable
if [ "${SKIP_PERMISSIONS_CHECK:-false}" = "true" ]; then
log_info "Skipping permissions check (SKIP_PERMISSIONS_CHECK=true)"
else
log_info "Setting permissions..."
CURRENT_UID="$(id -u)"
CURRENT_GID="$(id -g)"

# Recursive chown/chmod on small directories only (config, logs, database, models)
for dir in /etc/lightnvr /var/log/lightnvr \
/var/lib/lightnvr/data/database /var/lib/lightnvr/data/models; do
[ -d "$dir" ] || continue
chown -R "${CURRENT_UID}:${CURRENT_GID}" "$dir" 2>/dev/null || log_warn "Could not set ownership on $dir (may be on NFS)"
chmod -R 755 "$dir" 2>/dev/null || log_warn "Could not set permissions on $dir (may be on NFS)"
done

# For data directories that may contain hundreds of thousands of recording
# files, only fix the directory itself (non-recursive) to avoid an O(n)
# walk that can block startup for hours on large deployments (see #368).
for dir in /var/lib/lightnvr /var/lib/lightnvr/data \
/var/lib/lightnvr/data/recordings /var/lib/lightnvr/data/recordings/mp4 \
/var/lib/lightnvr/www; do
[ -d "$dir" ] || continue
chown "${CURRENT_UID}:${CURRENT_GID}" "$dir" 2>/dev/null || true
chmod 755 "$dir" 2>/dev/null || true
done

# Per-stream subdirectories (one per camera) — chown one level deep
# so a data volume mounted from a host with a different UID still ends
# up writable. Bounded by camera count, NOT file count: -mindepth 1
# -maxdepth 1 -type d skips the recording files inside. Even a
# 100-camera deployment touches at most ~200 entries here.
for parent in /var/lib/lightnvr/data/recordings /var/lib/lightnvr/data/recordings/mp4; do
[ -d "$parent" ] || continue
find "$parent" -mindepth 1 -maxdepth 1 -type d \
-exec chown "${CURRENT_UID}:${CURRENT_GID}" {} + 2>/dev/null || true
find "$parent" -mindepth 1 -maxdepth 1 -type d \
-exec chmod 755 {} + 2>/dev/null || true
done
fi

# Do not recursively chown or chmod mounted operator files. In particular,
# retain 0600 permissions on credentials and go2rtc overrides.
# Test write permissions on critical directories
log_info "Testing write permissions..."
for test_dir in "/var/lib/lightnvr/data" "/etc/lightnvr" "/var/log/lightnvr"; do
Expand All @@ -291,7 +250,8 @@ EOF
rm -f "$test_file" 2>/dev/null
log_info "Write permission OK: $test_dir"
else
log_error "No write permission: $test_dir - This may cause issues!"
log_error "No write permission: $test_dir - prepare volume ownership for UID:GID $(id -u):$(id -g)"
exit 1
fi
done

Expand Down Expand Up @@ -329,16 +289,16 @@ generate_go2rtc_config_from_settings() {

# Remove any stale PID file left over from a previous container run.
# When a container is stopped and restarted (without being recreated) the
# container filesystem is preserved, so /var/run/lightnvr.pid may still exist
# container filesystem is preserved, so /var/run/lightnvr/lightnvr.pid may still exist
# with the PID from the old run. In the new container the PID namespace
# restarts from 1, meaning that old PID can be reused by an unrelated process
# (e.g. go2rtc or a shell). If LightNVR then finds that PID it would send
# SIGTERM to the wrong process, crashing the container in a restart loop.
# Removing the stale file here, before LightNVR starts, prevents that.
cleanup_stale_pid_file() {
# Determine the PID file path. Try to read it from the config; fall back
# to the compiled-in default of /var/run/lightnvr.pid.
local pid_file="/var/run/lightnvr.pid"
# to the container default of /var/run/lightnvr/lightnvr.pid.
local pid_file="/var/run/lightnvr/lightnvr.pid"
if [ -f /etc/lightnvr/lightnvr.ini ]; then
local cfg_val
cfg_val=$(grep -E '^\s*pid_file\s*=' /etc/lightnvr/lightnvr.ini \
Expand Down
63 changes: 50 additions & 13 deletions docs/DOCKER.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
# Docker Deployment Guide

## Security migration: non-root runtime and private go2rtc API

Images run as UID:GID `10001:10001`. Port 1984 is internal and bound to loopback.
MSE/WebRTC now require the authenticated exact routes in [Reverse Proxy](REVERSE_PROXY.md),
with the proxy sharing LightNVR's network namespace. HLS and snapshots work through
LightNVR's authenticated HTTP proxy.

The repository Compose file uses named volumes for config, database, and models,
and a bind mount only for `./data/recordings`. Before the first start, prepare that
recordings directory for UID:GID 10001:10001. Existing deployments must retain or
migrate their config, database, and models before switching volume layouts; otherwise
Docker creates empty volumes and the recorder appears unconfigured. Stop the recorder
and back up the data before migrating. You can keep existing mounts during migration,
provided they contain only LightNVR state and are writable by the runtime UID.

For existing bind mounts, change ownership of the specific LightNVR directories to
10001:10001 on the host. The entrypoint no longer changes ownership or permissions
recursively. Preserve restrictive permissions on credentials and `override.yaml`.
Change an existing `pid_file = /var/run/lightnvr.pid` to
`pid_file = /var/run/lightnvr/lightnvr.pid` in `lightnvr.ini`.
Root-owned application binaries and web assets stay outside writable volumes.
Do not mount host roots, Docker sockets, or unrelated host data into the recorder.

The bundled go2rtc rejects `exec:`, `echo:`, and `expr:` sources, including config-file
streams and redirect results. FFmpeg sources accept network URLs or registered stream
names and supported codec/input presets; raw command fragments, local file/device
inputs, and arbitrary filter/query parameters are rejected. The supported synthetic
`virtual?video=...` test patterns accept only validated pattern parameters.


*Verified against LightNVR 0.37.x.*

This guide provides comprehensive information about deploying LightNVR using Docker.
Expand Down Expand Up @@ -39,15 +69,23 @@ services:
- "8554:8554" # RTSP
- "8555:8555" # WebRTC TCP
- "8555:8555/udp" # WebRTC UDP
- "1984:1984" # go2rtc API
volumes:
# DO NOT mount /var/lib/lightnvr directly - it will overwrite web assets!
- ./config:/etc/lightnvr
- ./data:/var/lib/lightnvr/data
- lightnvr-config:/etc/lightnvr
- lightnvr-database:/var/lib/lightnvr/data/database
- lightnvr-recordings:/var/lib/lightnvr/data/recordings
- lightnvr-models:/var/lib/lightnvr/data/models
security_opt:
- no-new-privileges:true
cap_drop: [ALL]
environment:
- TZ=UTC # Change to your timezone (e.g., America/New_York)
# ONVIF discovery network for containers, e.g. 192.168.1.0/24
# - LIGHTNVR_ONVIF_NETWORK=192.168.1.0/24
volumes:
lightnvr-config:
lightnvr-database:
lightnvr-recordings:
lightnvr-models:
```

Then start it:
Expand Down Expand Up @@ -76,9 +114,11 @@ docker run -d \
-p 8554:8554 \
-p 8555:8555 \
-p 8555:8555/udp \
-p 1984:1984 \
-v ./config:/etc/lightnvr \
-v ./data:/var/lib/lightnvr/data \
--security-opt no-new-privileges --cap-drop ALL \
-v lightnvr-config:/etc/lightnvr \
-v lightnvr-database:/var/lib/lightnvr/data/database \
-v lightnvr-recordings:/var/lib/lightnvr/data/recordings \
-v lightnvr-models:/var/lib/lightnvr/data/models \
-e TZ=America/New_York \
ghcr.io/opensensor/lightnvr:latest
```
Expand Down Expand Up @@ -157,9 +197,7 @@ NFS volumes may have different permission models than local filesystems. If you
device: ":/volume1/docker/lightnvr/config"
```

2. **UID/GID mapping**: The container runs as root by default. Ensure your NFS export allows root access or map UIDs appropriately:
- On Synology NAS: Enable "Map all users to admin" or "Squash" options in NFS permissions
- Or use `all_squash,anonuid=1000,anongid=1000` in NFS export options
2. **UID/GID mapping**: The container runs as UID:GID `10001:10001`. Grant that identity access to the dedicated LightNVR export, or map it to a dedicated NAS service account. Keep root squashing enabled.

3. **Directory pre-creation**: For better reliability with NFS, pre-create the directory structure on your NAS:
```bash
Expand All @@ -168,7 +206,7 @@ NFS volumes may have different permission models than local filesystems. If you
mkdir -p /volume1/docker/lightnvr/data/recordings/mp4
mkdir -p /volume1/docker/lightnvr/data/database
mkdir -p /volume1/docker/lightnvr/data/models
chmod -R 755 /volume1/docker/lightnvr
chown -R 10001:10001 /volume1/docker/lightnvr
```

#### Recording Issues on NFS
Expand Down Expand Up @@ -227,7 +265,7 @@ copy-on-first-run step. This means:
| 8080 | TCP | Web UI | Main web interface |
| 8554 | TCP | RTSP | RTSP streaming server |
| 8555 | TCP/UDP | WebRTC | WebRTC streaming |
| 1984 | TCP | go2rtc API | go2rtc REST API |
| 1984 | TCP | Internal go2rtc API | Loopback only; do not publish |

### Network Modes

Expand All @@ -241,7 +279,6 @@ services:
- "8554:8554"
- "8555:8555"
- "8555:8555/udp"
- "1984:1984"
```

#### Host Mode (For Better Performance)
Expand Down
Loading
Loading