Repository navigation
Document a recommended setup - #3
Merged
Merged
Conversation
The quick starts show the shortest thing that works, which is not the same as what to run if you want it to keep working. Three of the choices that matter are counter-intuitive enough that a new user would get them wrong, and all three came out of hardware testing rather than taste: * CAM2IP_LAZY=true, despite making cold grabs ~450ms slower. It stops cam2ip being able to serve a stale frame at all, self-heals an unplug in under a second where holding the device open needs a container restart, and turns the indicator light off when nothing is watching. * A /dev/v4l/by-id/ device path rather than /dev/video0. A devices: mapping is resolved once at container-create, and a replugged camera may come back on a different minor -- so the default needs a compose edit where a by-id path needs only a restart. * Watching camera_status.state rather than the container healthcheck. The healthcheck deliberately never touches the camera, so an unplugged camera leaves the container healthy and both connection booleans true. Only the state field says connected_but_no_frames. Also states the three -- and only three -- reasons to touch the frame settings, since the defaults were measured with three frames of margin on every platform tested and are otherwise best left alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The compose snippet set MCP_HTTP_HOST=127.0.0.1, which binds the server to loopback inside the container. Docker forwards a published port to the container's bridge address, not its loopback, so nothing on the host could reach the MCP endpoint. It fails quietly: the healthcheck runs inside the container and connects to loopback, which is exactly where the server is listening, so it passes and `docker ps` keeps reporting healthy. That is the same healthy-but-unreachable state the section's own "Ports" paragraph warns about, reached by a different route. Drop the variable so the image's 0.0.0.0 default applies, and rewrite the paragraph around what the actual boundary is: the 127.0.0.1: prefix on the host side of the ports mapping. Spell out the consequence too -- a server not bound to loopback gets no automatic Host allowlist from mcp, so MCP_ALLOWED_HOSTS is the knob once the publish widens past loopback. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a "Recommended setup" section to the README, between the quick starts and
"How frames stay fresh".
The quick starts show the shortest thing that works. Nothing said what to run if
you want it to keep working, so the non-obvious choices — the ones the live
hardware testing turned up — were only recorded in the frame-freshness deep dive
or in comments in
docker-compose.yml, where nobody setting the thing up for thefirst time will look.
Documentation only; no code, no behaviour change.
What it covers
A compose file to copy, then the reasoning for each choice that isn't obvious:
macOS and Windows cannot see a webcam and fails outright on the device mapping.
CAM2IP_LAZY=true— the counter-intuitive one, since it makes cold grabs~450ms slower. It buys the device being released when nobody is subscribed:
cam2ip cannot serve a stale frame at all, an unplug/replug self-heals in under
a second instead of needing a container restart, and the indicator light goes
off when nobody is looking.
/dev/v4l/by-id/device path —devices:resolves once atcontainer-create, so after a replug the container points at a minor number the
kernel has moved on from. The by-id symlink follows the device, so a plain
restart finds it. The
/devbind-mount alternative is mentioned with its cost(every device on the machine) rather than recommended.
warm stream (~10ms vs ~700ms grabs), and the restart policy and healthcheck
only mean something in HTTP mode.
127.0.0.1from the host side, while leaving the in-containerbind at its
0.0.0.0default — the two are separate questions and only thefirst is the security boundary. Docker forwards a published port to the
container's bridge address, not its loopback, so binding the server to
127.0.0.1inside the container makes it unreachable from the host — quietly,because the healthcheck runs inside the container against loopback, passes,
and leaves
docker psreporting healthy. The section also notes theconsequence: a server not bound to loopback gets no automatic Host allowlist
from mcp, so
MCP_ALLOWED_HOSTSis the knob once the publish widens pastloopback — and it becomes required outright the moment
MCP_ALLOWED_ORIGINSis set, since origins alone leaves an empty host allowlist that rejects
everything.
.envso both sides move together, and a pinnedsha-tag rather than
latest.Then two subsections:
against a queue that holds 4. Lists the only three reasons to touch them, and
notes
uvcvideowas measured granting exactly the buffer count requested atevery value from 1 to 32, so raising
MCP_WARMUP_FRAMESbuys nothing there.camera_status→state, not the containerhealthcheck. The healthcheck deliberately never touches the camera, because
probing a frame would dequeue one and wake the device every interval, defeating
CAM2IP_LAZY. An unplugged camera therefore leaves the container healthy andboth of
camera_status's connection booleanstrue;stateis what readsconnected_but_no_frames.Verification
test,image-smokeandbuild-and-pushallpassed. The follow-up revision touches only
README.md.docker compose config, which confirms it resolves tohost_ip: 127.0.0.1onboth publishes and to an
environment:block that sets no bind addressdocker compose configon the repo's owndocker-compose.ymlstill parses#on-macoscross-reference resolves, the/devbind-mount notes referredto do exist in
docker-compose.yml, and every env var named(
CAM2IP_LAZY,MCP_WARMUP_S,MCP_WARMUP_FRAMES,MCP_STREAM_IDLE_S,MCP_ALLOWED_HOSTS,MCP_ALLOWED_ORIGINS) is one the code actually reads