Remote tailnet model endpoints¶
Anvil Serving can prepare a portable endpoint bundle for a GPU VM or host that
runs Docker Compose. The bundle puts an OpenAI-compatible inference container in
the Tailscale container's network namespace and exposes only /v1 through
Tailscale Serve. It does not publish a host port or enable Funnel.
This surface is an offline preview. It does not rent hardware, pull images, start containers, create an auth key, join a tailnet, change grants, or update a router. A valid render is not proof of provider compatibility or a working deployment.
Validate and render¶
Start with examples/remote-tailnet/compose-endpoint.json.
The registry.example image references and synthetic model revision are inert
placeholders. Replace each image with the intended image tag and registry
digest, and replace the model revision with an immutable revision accepted by
the selected server command.
anvil-serving edge bundle validate --manifest examples/remote-tailnet/compose-endpoint.json
anvil-serving edge bundle render --manifest examples/remote-tailnet/compose-endpoint.json
Both commands print JSON and write nothing. The global --json envelope labels
these operations by command name without echoing the manifest operand.
render returns three file
payloads:
compose.jsonis a Docker Compose document for a Compose-capable GPU VM or host. The inference service usesnetwork_mode: service:tailscale, binds its server to127.0.0.1, and publishes no ports.tailscale-config/serve.jsonis the file referenced byTS_SERVE_CONFIG. Its only handler is/v1, its backend ishttp://127.0.0.1:<port>/v1, and it contains noAllowFunnelentry.router-tier.tomlis an engine-agnostic tier and alias fragment. Replace the synthetic tailnet DNS placeholder after enrollment and review it before merging it into an operator-owned router configuration.
Tailscale Serve removes the matched mount from the incoming path before it
joins the remainder to the proxy URL. The proxy URL therefore retains /v1:
an incoming /v1/models becomes the backend's /v1/models, rather than
/models. The current behavior is visible in Tailscale's
serveWebHandler and reverse-proxy source at revision 5201273a.
The manifest is closed: unknown, missing, or duplicate fields fail validation. The first
adapter is deliberately narrow: inference.runtime must be vllm-openai.
The renderer constructs vLLM's model and tokenizer revisions, served name,
127.0.0.1 host, port, context limit, and cache directory from typed fields.
extra_args accepts only separate canonical tuning flags: --tensor-parallel-size,
--gpu-memory-utilization, --dtype, --enable-auto-tool-choice,
--tool-call-parser, and --enable-request-id-headers. Duplicate flags,
abbreviations, underscore aliases, configuration files, and interpolation fail
validation. tool_support: true requires both auto-tool-choice and a declared
tool parser; the example keeps it false. Tool correctness still requires
qualification with the pinned engine and model. These restrictions constrain
the generated launch arguments; a trusted image must actually honor them.
Engine knowledge remains in this adapter, outside the router tier. Image
references must end in @sha256: plus a 64-character lowercase digest. The
repository name uses Docker's component syntax; empty and traversal segments
fail validation. Registry ports and IPv6 literals are outside this first
adapter's supported image-reference subset. See the
distribution reference grammar.
The model revision must be a 40- or 64-character lowercase hexadecimal object ID.
served_model must be a canonical remote repository ID, organization/model;
local filesystem models are outside this adapter's revision contract.
The adapter accepts up to 96 ASCII characters in each segment, requires an
alphanumeric start, forbids trailing - or ., repeated -- or .., and a
.git suffix. The hub also validates repository syntax; existence, revision
availability, and access rights require deployment-time checks. See the
official repository validator.
Environment fields contain names only. Tailnet auth and inference auth must use
different names, and tailnet state cannot share the model cache volume. At
Compose evaluation time, the
rendered document requires those variables to exist without recording their
values.
The vLLM adapter requires api_key_container_env: "VLLM_API_KEY" and
cache_mount: "/root/.cache/huggingface". A different environment name would
not enable vLLM authentication; an arbitrary mount could hide runtime files.
The tailnet state volume is persistent because a Tailscale container without a
persistent TS_STATE_DIR registers as a new node after restart. Keep the auth
key outside the repository. Use a tagged, ephemeral auth key and limit the tag
with tailnet grants to only the clients and ports that need inference access.
See Tailscale's Docker parameters,
auth-key guidance,
and inference-server guide.
The example command uses vLLM's OpenAI-compatible server shape. vLLM documents
the vllm/vllm-openai container and --api-key/VLLM_API_KEY, but its security
guide warns that several non-OpenAI endpoints are not protected by that API
key. Exposing only /v1 is therefore a required boundary, not a convenience.
Review the current vLLM container instructions
and security guidance before
pinning an image.
Vast.ai boundary¶
A standard Vast.ai rental is one Docker container. Vast's current technical FAQ
states that Docker-in-Docker is disabled and recommends a multi-service image or
separate instances when several services are needed. That shape cannot run the
two-service Compose bundle, so a manifest with target: "vast-container"
returns the typed unsupported-target result.
A Vast-specific deployment needs a separately designed and qualified custom
image that supervises both tailscaled/containerboot and the inference server in
one container, retains state across the provider's lifecycle, handles signals
and failures correctly, and still exposes only the /v1 Serve path. Vast's
template settings, GPU runtime, storage, and secret injection also need
provider-side qualification. This repository has not performed that deployment
test. See Vast's technical FAQ,
networking guide,
and template settings.
Promotion gate¶
Readiness sends the configured upstream bearer credential to /v1/models when
it is available. Existing anonymous local tiers remain supported. For a remote
endpoint, verify that the same path rejects an unauthenticated request and
accepts the intended credential; an anonymous health success does not prove
authentication is enforced.
After rendering, an operator still needs to:
- Review and materialize the three payloads outside the public repository.
- Create the tagged ephemeral auth key and tailnet grant without placing either secret in a tracked file.
- Start the Compose stack on a qualified host and confirm its exact image and model revisions.
- Verify authenticated
GET /v1/modelsover the tailnet, then use the normal router preflight and benchmark gates before adding the tier to a live route.
Provider launch, tailnet enrollment, router configuration, and route promotion remain separate operator actions.
The Serve document declares only a /v1 handler. The cited Tailscale source
rejects paths that clean outside that mount. Provider/backend behavior across
the complete path, including additional URL decoding, remains a deployment
acceptance check.