Amcrest-PTZ-Bridge

Amcrest-PTZ-Bridge

Docker app from gaming09's Repository

Overview

Expose the local DVRIP pan and tilt controls of an Amcrest SmartHome camera as an ONVIF PTZ service for Frigate. Video and audio remain direct between the camera and Frigate; this bridge handles movement commands only. Frigate configuration (add this block to the existing camera definition): cameras: patio_camera: # Existing ffmpeg, detect, objects, zones, and other settings stay here. # Add this block to this existing camera definition. onvif: host: 192.0.2.10 # Replace with the Unraid server LAN address. port: 18880 # Match this bridge instance's ONVIF host port. user: "" password: ""

Amcrest PTZ Bridge - VibeCoded

Amcrest PTZ Bridge exposes the local Dahua DVRIP pan and tilt controls of an Amcrest SmartHome camera as a small ONVIF service that Frigate can use. Video and audio continue to flow directly between the camera and Frigate/go2rtc; this container handles movement commands only. I absolutely hate the amcrest smarthome app and want to use it as little as possible so i wanted all ptz controls through frigate so i vibecoded this. It doesnt reach out to the internet, it doesnt send creds anywhere.

The project is designed for one camera per container instance. Install another instance with a different name and host port for each additional camera.

Data flow

Frigate PTZ controls
        |
        | ONVIF ContinuousMove / Stop
        v
Amcrest PTZ Bridge :18880
        |
        | local authenticated DVRIP
        v
Amcrest camera :37777

Every movement has a configurable fail-safe timer, so the bridge stops the camera even if the ONVIF client loses its connection before sending Stop.

Unraid installation

Search for Amcrest PTZ Bridge in Unraid Community Applications and install it from the app listing.

Configure:

  • a unique ONVIF host port, beginning with 18880;
  • the camera's LAN IP address;
  • a local camera username and either a password or the optional secret-file settings;
  • optional movement timeout and client allowlist settings.

For a second camera, install a second instance and use port 18881, then 18882, and so on.

Password choices

The App Store form includes both supported credential methods; you do not need to use Add another Path, Port, Variable, Label or Device.

  • Standard: enter Camera password and leave both advanced secret-file fields blank.
  • Secret file: create a host file containing only the camera password, leave Camera password blank, select that file in Camera password secret file, and set Camera password file to /run/secrets/camera_password. The host file must be readable by container user 10001; restrict it to that user where your storage permissions allow.

Both secret-file settings are optional and blank in the published template. The template never contains a camera address, username, password, or a developer-specific host path.

Connect the bridge to Frigate

The bridge does not replace the camera's existing ffmpeg or go2rtc stream configuration. It adds the ONVIF endpoint that Frigate uses for the PTZ buttons.

  1. In Frigate, open Settings > Configuration Editor. If you edit the file directly, use config.yml in the host directory mapped to Frigate's /config directory (commonly /mnt/user/appdata/frigate/config.yml on Unraid).
  2. Find the existing camera under the top-level cameras: section.
  3. Add onvif: inside that camera, at the same indentation level as ffmpeg:, detect:, objects:, and zones:. Do not add a second copy of the camera name and do not put onvif: inside ffmpeg:.
  4. Set host to the Unraid server's LAN address, not the camera's address. Set port to the ONVIF host port chosen for this bridge container.

The following example uses the documentation-only address 192.0.2.10; replace it with your Unraid server address. Keep the camera's existing stream and detection keys unchanged.

cameras:
  patio_camera:
    # Existing ffmpeg, detect, objects, zones, and other settings stay here.

    # Add this block to this existing camera definition.
    onvif:
      host: 192.0.2.10  # Replace with the Unraid server LAN address.
      port: 18880       # Match this bridge instance's ONVIF host port.
      user: ""
      password: ""

The bridge does not require ONVIF authentication, but Frigate may require the explicit empty user and password values. Validate the configuration, then use Save & Restart. Open that camera's Live page after Frigate restarts; the PTZ controls should be available there.

For additional cameras, install one bridge instance per camera and assign each instance a unique host port (18880, 18881, and so on). Add the corresponding onvif: block under each Frigate camera. Do not point Frigate at the camera's DVRIP port 37777; that connection is made privately by the bridge.

If the controls do not appear, open http://UNRAID_SERVER_IP:ONVIF_HOST_PORT/health from your LAN and confirm that it reports healthy, then check the Frigate logs for ONVIF connection errors. This bridge implements basic continuous pan/tilt and stop commands; it does not add optical zoom, presets, or Frigate autotracking support.

For Frigate's complete ONVIF settings, see the official camera configuration documentation.

Configuration

Environment variable Default Purpose
CAMERA_HOST required Camera LAN IP address or hostname
CAMERA_USERNAME admin Local camera account
CAMERA_PASSWORD empty Local camera password; required unless CAMERA_PASSWORD_FILE is used
CAMERA_PASSWORD_FILE empty Optional file containing the password; takes precedence over CAMERA_PASSWORD
CAMERA_NAME amcrest-camera ONVIF device/profile name
CAMERA_MODEL Amcrest SmartHome PTZ Model string reported through ONVIF
CAMERA_PORT 37777 Dahua DVRIP port
CAMERA_CHANNEL 0 DVRIP channel
LISTEN_PORT 18880 Internal ONVIF listener port
MAX_MOVE_SECONDS 1.25 Movement fail-safe timeout
ALLOWED_CLIENTS empty Optional comma-separated IP/CIDR allowlist
LOG_LEVEL INFO Python log level

When ALLOWED_CLIENTS is empty, any client able to reach the published Docker port can issue PTZ commands. Docker NAT can make local clients appear as a 172.16.0.0/12 gateway address, so test an allowlist carefully. Loopback health checks are always permitted.

Advanced YAML mode

Set BRIDGE_CONFIG=/config/config.yml and mount a YAML file to use the original multi-camera configuration mode. The Community Applications template intentionally uses environment variables and one instance per camera because that produces a safer and clearer Unraid installation form.

Network and internet access

At runtime the application listens for ONVIF requests, connects to the configured camera over local TCP port 37777, and checks its own localhost health endpoint. It contains no telemetry, advertising, or cloud integration. The image explicitly disables the optional update checker inherited from the pwntools dependency, so it does not perform a runtime version check.

Building or updating the image requires internet access to pull the Python base image, download the pinned MIT-licensed DahuaConsole source, and install Python dependencies. Unraid also contacts GHCR to pull images and check for updates. Docker bridge networking technically permits outbound internet access unless the administrator adds an egress firewall rule.

The www.onvif.org strings in bridge.py are XML namespace identifiers; they are not outbound web requests.

Local Docker run

docker run -d \
  --name amcrest-ptz-bridge \
  -p 18880:18880/tcp \
  -e CAMERA_HOST=192.0.2.25 \
  -e CAMERA_USERNAME=admin \
  -e CAMERA_PASSWORD='replace-me' \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=16m \
  --cap-drop=ALL \
  --security-opt=no-new-privileges:true \
  ghcr.io/gaming09/amcrest-ptz-bridge:latest
python -m pip install -r requirements-dev.txt
python -m unittest discover -s tests -v
python scripts/validate_repository.py
docker build -t amcrest-ptz-bridge:dev .

Security and credentials

The published app template ships with a blank camera password field and contains no developer camera address, name, username, or credential. Each installer must provide credentials for their own camera.

After installation, Unraid stores a standard password locally in that server's per-container DockerMan template and Docker configuration so the existing container can be edited or recreated. Depending on the Unraid version, an administrator may be able to view that local value on the Edit page or through Docker inspection. It is never uploaded to this repository by the app.

For file-based secret handling, use the built-in advanced Camera password secret file and Camera password file fields described above. The bridge reads the file at runtime, and the real password does not need to be stored in a Docker environment variable.

The container runs as an unprivileged user, drops Linux capabilities in the Unraid template, uses a read-only root filesystem, and does not require access to Unraid shares or the Docker socket.

See SECURITY.md for vulnerability reporting.

License and attribution

Bridge code is MIT licensed. The image uses a pinned revision of the MIT-licensed mcw0/DahuaConsole project for DVRIP connectivity. See THIRD_PARTY_NOTICES.md.

This project is not affiliated with or endorsed by Amcrest, Dahua, Frigate, or Unraid.

Install Amcrest-PTZ-Bridge on Unraid in a few clicks.

Find Amcrest-PTZ-Bridge in Community Apps on your Unraid server, review the template, and click Install. Unraid handles the Docker app or plugin setup from the published template.

Open the Apps tab on your Unraid server Search Community Apps for Amcrest-PTZ-Bridge Review the template variables and paths Click Install

Requirements

An Amcrest SmartHome PTZ camera reachable from Unraid over local TCP port 37777. Configure one container instance per camera and assign each instance a unique host port.

Related apps

Details

Repository
ghcr.io/gaming09/amcrest-ptz-bridge:latest
Last Updated2026-09-08
First Seen2026-08-14

Runtime arguments

Web UI
http://[IP]:[PORT:18880]/health
Network
bridge
Shell
sh
Privileged
false
Extra Params
--read-only --tmpfs /tmp:rw,noexec,nosuid,size=16m --cap-drop=ALL --security-opt=no-new-privileges:true

Template configuration

ONVIF host portPorttcp

Port Frigate uses to reach this camera's ONVIF PTZ facade. Use a unique host port for each installed camera.

Target
18880
Default
18880
Value
18880
Camera nameVariable

Friendly name reported by the ONVIF service.

Target
CAMERA_NAME
Default
amcrest-camera
Value
amcrest-camera
Camera IP or hostnameVariable

LAN address of the Amcrest camera, for example 192.0.2.25 (replace this documentation-only address).

Target
CAMERA_HOST
Camera usernameVariable

Local camera account with PTZ permission.

Target
CAMERA_USERNAME
Default
admin
Value
admin
Camera passwordVariable

Local camera password. Leave blank when using the optional secret-file settings below. This is masked in the form but remains available to Docker as an environment value.

Target
CAMERA_PASSWORD
Camera password secret filePath

Optional host file containing only the camera password. When used, choose the host file here and set Camera password file to /run/secrets/camera_password. Leave blank to use Camera password.

Target
/run/secrets/camera_password
Camera password fileVariable

Optional container path for the mounted password file. Set to /run/secrets/camera_password when using Camera password secret file; otherwise leave blank.

Target
CAMERA_PASSWORD_FILE
Camera DVRIP portVariable

Local Dahua DVRIP service port. Most Amcrest SmartHome cameras use 37777.

Target
CAMERA_PORT
Default
37777
Value
37777
Camera modelVariable

Model label reported to ONVIF clients.

Target
CAMERA_MODEL
Default
Amcrest SmartHome PTZ
Value
Amcrest SmartHome PTZ
Camera channelVariable

DVRIP camera channel. Standalone cameras normally use channel 0.

Target
CAMERA_CHANNEL
Default
0
Value
0
Maximum move timeVariable

Fail-safe maximum movement duration in seconds if an ONVIF Stop request is lost.

Target
MAX_MOVE_SECONDS
Default
1.25
Value
1.25
Allowed clientsVariable

Optional comma-separated IP/CIDR allowlist. Leave empty to accept any client that can reach the published LAN port. Docker NAT may present clients as a 172.16.0.0/12 gateway address.

Target
ALLOWED_CLIENTS
Log levelVariable

Python log level: DEBUG, INFO, WARNING, or ERROR.

Target
LOG_LEVEL
Default
INFO
Value
INFO