Before publishing your Docker Compose file, ensure all container images referenced in your compose file are pushed to their registries (ECR, Docker Hub, etc.). These images must be available when you create a release.
After a few minutes, the deployment stack downloads to a directory named my-test-appliance, alongside a companion my-test-appliance.audit directory containing the audit stack for pre-deployment review.
2
Review the audit stack (optional)
Before deploying, you can scan the audit stack with your standard IaC security and policy tooling. The audit stack is a Tensor9-free copy of the deployment stack - same application infrastructure, without any Tensor9 providers or runtime plumbing - so scanners can plan and inspect it standalone:
By default, the listeners your CLI and Terraform use to reach the vendor controller are exposed on a public network load balancer with mTLS protection. If you operate a Tailscale tailnet for your engineering team, you can have the vendor controller join that tailnet and (optionally) remove the public path entirely. See Connectivity for the broader picture.This workflow rolls out in two phases: first attach the controller to your tailnet and verify operator access, then optionally enforce tunnel-only by removing the public listeners.
Phase 1: attach the vendor controller to your tailnet
1
Ensure your tailnet ACL has the required tags and rules
This step is idempotent. It adds the tag:tensor9-vctrl (vendor controller) and tag:tensor9-customer-ctrl (appliance controller) tags, the group:tensor9-operators group, and the rules that allow operators and customer appliances to reach the vendor controller. Existing ACL entries are preserved.
If you maintain ACLs in HuJSON via the dashboard, the command prints a copy-pasteable snippet you can apply by hand. Otherwise answer y at the confirmation prompt to have the CLI POST the change for you (a local backup is written under ~/.tensor9/tailscale-acl-backups/).See tensor9 tailscale acl setup for full options.
2
Generate a pre-auth key for the vendor controller
The key is tagged so the ACL applies the right rules to the resulting node. Keep it single-use (the default) for production:
tensor9 tailscale key generate -tag VCtrl
Save the printed tskey-auth-... value; you will pass it to the next command. See tensor9 tailscale key generate for full options.Add operators to the group:operators group in the Tailscale dashboard so they can reach the controller’s listeners over the tailnet.
The command installs the Tailscale daemon on the vendor controller, joins it to the tailnet, and stamps the resulting tailnet hostname onto the controller’s configuration. The controller’s existing public listeners stay in place.See tensor9 vendor tailscale onboard for full options.
4
Verify operator access over the tailnet
With your operator account in group:operators and connected to the tailnet, run a read-only command:
tensor9 report
Your CLI should be able to reach the vendor controller over the tailnet without any extra flags. The controller is still reachable over the public path too, so this step proves the tailnet route is working while leaving you a fallback.The endpoint used with Terraform/OpenTofu plan and apply operations is defined in the tensor9 provider block of the compiled deployment stack. Compilation emits the publicly available listener to this block when it is available, even if you have a tunnel configured, to be compatible with CI deployments. For testing, you can manually modify the compiled stack to change the endpoint address to be the vendor controller’s address on the tailnet and try a plan. Follow phase 2, below, to remove the TF endpoint from the public load balancer and re-compile your deployment stacks to enforce using the Tailscale for communication between your local Terraform/OpenTofu CLI and the vendor controller.
Once you are satisfied that operators and Terraform-driven deploys work over the tailnet, you can enforce tunnel-only and tear the public listeners down. Roll this out per listener group rather than all at once so you can pause if something is missed.
1
Mark the operator listeners as tunnel-only
The tunnel enforce command mutates the controller’s configuration; the listener teardown happens on the next infrastructure upgrade.
# CLI listeners firsttensor9 vendor tunnel enforce -add CLI# After a soak period, the Terraform reactor too (make sure CI works!)tensor9 vendor tunnel enforce -add Terraform
See tensor9 vendor tunnel enforce for full options. If you also want to remove the appliance-facing public listener, the command checks first that no customer appliance still depends on it; see the pre-flight notes in the reference.
2
Apply the change
Run an infrastructure upgrade to remove the now-redundant listeners from your network load balancer:
tensor9 vendor upgrade \ -kind Infrastructure \ -reason "remove public CLI and Terraform listeners after Tailscale cutover"
After this completes, the only way to reach the enforced listeners is over the tailnet.
Make sure every operator and every CI runner that drives tensor9 or terraform against the vendor controller is on the tailnet before you run tunnel enforce -add CLI / Terraform. After the next infrastructure upgrade, anything off the tailnet will be locked out.
To roll back, run tensor9 vendor tunnel enforce -remove CLI,Terraform followed by tensor9 vendor upgrade -kind Infrastructure. The public listeners are recreated.