Deployment
Docker Compose
Section titled “Docker Compose”The repository’s compose.yaml is a good starting point:
services: unifi-mcp: image: ghcr.io/mattoddie/unifi-mcp:latest container_name: unifi-mcp restart: unless-stopped env_file: .env ports: - "8080:8080"docker compose up -d # startdocker compose logs -f # follow logsdocker compose ps # shows "healthy" once the health check passesdocker compose down # stopThe image has a built-in health check that calls /health every 30 seconds.
To listen on one interface only, such as a LAN address or localhost when you use a reverse proxy, change the port mapping to "192.168.1.10:8080:8080" or "127.0.0.1:8080:8080".
Image tags
Section titled “Image tags”Images are published to the GitHub Container Registry at ghcr.io/mattoddie/unifi-mcp for linux/amd64 and linux/arm64. Each GitHub release publishes these tags:
| Tag | Example | Moves? |
|---|---|---|
X.Y.Z |
1.4.2 |
Never. Pin this for full reproducibility. |
X.Y |
1.4 |
Moves to the latest patch release (bug fixes). |
X |
1 |
Moves to the latest minor release (new features, no breaking changes). Not published for 0.x. |
latest |
Moves to the newest stable release. |
Pre-releases, such as 1.5.0-rc.1, are only published under their exact version, never as latest.
Every image includes an SBOM and build provenance attestation. To verify that an image was built by this repository’s release workflow:
gh attestation verify oci://ghcr.io/mattoddie/unifi-mcp:1.4.2 --owner mattoddieUpgrading
Section titled “Upgrading”docker compose pulldocker compose up -dCheck the changelog or the release notes before upgrading across a major version.
Building from source
Section titled “Building from source”git clone https://github.com/mattoddie/unifi-mcp.gitcd unifi-mcpdocker build -t unifi-mcp .The tests run as part of the image build, so a broken build never produces an image. To use a local build with Compose, replace image: in compose.yaml with build: . and run docker compose up -d --build.
TLS with a reverse proxy
Section titled “TLS with a reverse proxy”The server speaks plain HTTP. If it’s reachable from outside a trusted network, put a TLS-terminating reverse proxy in front of it and keep MCP_AUTH_TOKEN set.
Caddy, which gets certificates automatically:
unifi-mcp.example.com { reverse_proxy unifi-mcp:8080}nginx:
server { listen 443 ssl; server_name unifi-mcp.example.com; ssl_certificate /etc/ssl/certs/unifi-mcp.pem; ssl_certificate_key /etc/ssl/private/unifi-mcp.key;
location /mcp { proxy_pass http://unifi-mcp:8080; proxy_set_header Host $host; proxy_read_timeout 120s; # some tools (backups, RF scans) take a while }}Traefik labels:
services: unifi-mcp: image: ghcr.io/mattoddie/unifi-mcp:latest env_file: .env labels: - traefik.enable=true - traefik.http.routers.unifi-mcp.rule=Host(`unifi-mcp.example.com`) - traefik.http.routers.unifi-mcp.tls.certresolver=letsencrypt - traefik.http.services.unifi-mcp.loadbalancer.server.port=8080When the server sits behind a proxy, don’t publish its port. Set MCP_ALLOWED_HOSTS=unifi-mcp.example.com so it only accepts requests for that hostname.
Hardening
Section titled “Hardening”The image already runs as the unprivileged node user and doesn’t need a writable filesystem. You can lock it down further:
services: unifi-mcp: image: ghcr.io/mattoddie/unifi-mcp:1.4.2 read_only: true cap_drop: [ALL] security_opt: [no-new-privileges:true] mem_limit: 256m env_file: .env ports: ["127.0.0.1:8080:8080"]See Security for choosing permissions and toolsets.
Running without Docker
Section titled “Running without Docker”Docker is the supported way to run unifi-mcp, but the server is an ordinary Node.js 22+ program:
git clone https://github.com/mattoddie/unifi-mcp.gitcd unifi-mcpnpm ci && npm run buildUNIFI_URL=https://192.168.1.1 UNIFI_API_KEY=... node dist/index.js # stdio by defaultMCP_TRANSPORT=http UNIFI_URL=... UNIFI_API_KEY=... node dist/index.js