{"id":41982955,"url":"https://github.com/intelecy/ztsc","last_synced_at":"2026-01-26T00:00:18.827Z","repository":{"id":54412424,"uuid":"266564585","full_name":"Intelecy/ztsc","owner":"Intelecy","description":"ZeroTier + Caddy sidecar","archived":false,"fork":false,"pushed_at":"2022-02-10T09:27:35.000Z","size":2130,"stargazers_count":20,"open_issues_count":0,"forks_count":3,"subscribers_count":4,"default_branch":"master","last_synced_at":"2023-03-21T20:20:06.001Z","etag":null,"topics":["caddyserver","docker","kubernetes","nomad","zerotier"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Intelecy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-05-24T15:02:17.000Z","updated_at":"2023-02-28T16:20:29.000Z","dependencies_parsed_at":"2022-08-13T14:50:56.256Z","dependency_job_id":null,"html_url":"https://github.com/Intelecy/ztsc","commit_stats":null,"previous_names":[],"tags_count":null,"template":null,"template_full_name":null,"purl":"pkg:github/Intelecy/ztsc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Intelecy%2Fztsc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Intelecy%2Fztsc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Intelecy%2Fztsc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Intelecy%2Fztsc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Intelecy","download_url":"https://codeload.github.com/Intelecy/ztsc/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Intelecy%2Fztsc/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28761863,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-25T23:06:19.311Z","status":"ssl_error","status_checked_at":"2026-01-25T23:03:50.555Z","response_time":113,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["caddyserver","docker","kubernetes","nomad","zerotier"],"created_at":"2026-01-26T00:00:17.145Z","updated_at":"2026-01-26T00:00:18.820Z","avatar_url":"https://github.com/Intelecy.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ztsc\nZeroTier + Caddy sidecar\n\n![Docker Image CI](https://img.shields.io/github/workflow/status/intelecy/ztsc/Docker%20Image%20CI?logo=github\u0026style=for-the-badge)\n[![image size](https://img.shields.io/docker/image-size/intelecy/ztsc/latest?logo=docker\u0026style=for-the-badge)](https://hub.docker.com/r/intelecy/ztsc)\n[![version](https://img.shields.io/docker/v/intelecy/ztsc?logo=docker\u0026sort=semver\u0026style=for-the-badge)](https://hub.docker.com/r/intelecy/ztsc)\n\n## Overview\n\n**tl;dr:** quick'n'dirty secure/remote access to one-off apps\n\n![handdrawn art](overview.png)\n\n`ztsc` is a sidecar container that uses [ZeroTier](https://www.zerotier.com/) and [Caddy](https://caddyserver.com/) to\nexpose one or more services running within a container orchestration platform like Nomad or Kubernetes.\n\n`ztsc` was built to quickly bridge the gap between \"Hey, check out this cool datascience* app I built. It's running on\nmy laptop.\" and \"Hey devops (team of one), can you host this somewhere for the next month?\".\n\n**Example**: A data scientist creates a [streamlit](https://www.streamlit.io/) app, and wants to share it internally with\nseveral other employees. Devops has a Nomad cluster, but can't be bothered with setting up a new load balancer, Azure \ninfrastructure, or any other dedicated networking. With `ztsc`, the app is hosted within Nomad, and is accessible\nto anyone on that ZeroTier network. No need to add any cloud infrastructure, nor grant direct (VPN) user access to ports\non your Nomad cluster.\n\n[*] Doesn't have to be datascience. But 99% of the time, it's a data scientist.\n\n## Configuration\n\nConfiguration of the ZeroTier sidecar requires:\n\n1) A ZeroTier network ID\n2) A previously generated ZeroTier identity (see `demo/Makefile`)\n3) A Caddyfile\n\nSee:\n\n* [Getting Started with ZeroTier](https://zerotier.atlassian.net/wiki/spaces/SD/pages/8454145/Getting+Started+with+ZeroTier)\n* [Caddyfile Tutorial](https://caddyserver.com/docs/caddyfile-tutorial)\n\n## Demo\n\nSee Docker Compose and Nomad [demo](demo/README.md)\n\n### Caddyfile\n\nSince the full power of Caddy is available, that means that more complex features can be used.\n\nFor example:\n\n* add automatic TLS using Let's Encrypt and DNS challenges\n* add HTTP auth for \"security\"\n* load balance between multiple service instances for horizontal scaling (remember there's still only a single `ztsc`\n  instance)\n\n### Environmental Variables\n\n* `ZT_NETWORK_ID` (required) -- 16 byte network address of the ZeroTier network you want to join. Multiple networks\n    can be joined by separating them with a space.\n* `CADDYFILE_PATH` (optional, default `/etc/caddy/Caddyfile`) -- path to Caddyfile.\n* `ZT_IDENTITY_PUBLIC` (optional) -- public part of ZeroTier identity.\n* `ZT_IDENTITY_PUBLIC_PATH` (optional, default `/var/lib/zerotier-one/identity.public`) -- path to file containing\n    public part of ZeroTier identity.\n* `ZT_IDENTITY_SECRET` (optional) -- secret part of ZeroTier identity.\n* `ZT_IDENTITY_SECRET_PATH` (optional, default `/var/lib/zerotier-one/identity.secret`) -- path to file containing\n    secret part of ZeroTier identity.\n\n\u003e Note: both the public and secret parts of the ZeroTier identity must be set. Either by passing them in as a string, a\n\u003e named file, or mounted over the default location. \n\n### Docker Requirements\n\nThe ZeroTier sidecar requires `NET_ADMIN` and `SYS_ADMIN` capabilities as well as access to `/dev/net/tun`.\n\nTo test that you have the correct privileges:\n\n```\n$ docker run --rm -it --device /dev/net/tun --cap-add NET_ADMIN --cap-add SYS_ADMIN \\\n    -e ZT_NETWORK_ID=001122334455667788 \\\n    -e ZT_IDENTITY_PUBLIC=1234567890 \\\n    -e ZT_IDENTITY_SECRET=1234567890 \\\n    intelecy/ztsc:latest\n\nZeroTier identity: 1234567890\n200 join OK\nwaiting for ZeroTier...\nwaiting for ZeroTier...\nwaiting for ZeroTier...\nZeroTier assigned addresses: 10.147.18.210/24\nstarting Caddy server...\n```\n\nIn another shell:\n\n```\n$ curl 10.147.18.210\nHello, world! \n```\n\n### Error Messages\n\n`ERROR: unable to configure virtual network port: could not open TUN/TAP device: No such file or directory`\n\nMissing `--device /dev/net/tun`\n\n---\n\n`ERROR: unable to configure virtual network port: unable to configure TUN/TAP device for TAP operation`\n\nMissing `--cap-add NET_ADMIN`\n\n---\n\n## Integrations\n\n### Docker Compose\n\n```yaml\nversion: \"3.7\"\nservices:\n  ztsc:\n    image: intelecy/ztsc:latest\n    environment:\n      - ZT_NETWORK_ID: \"001122334455667788\"\n    cap_add:\n      - NET_ADMIN\n      - SYS_ADMIN\n    devices:\n      - /dev/net/tun\n    init: true\n    volumes:\n      - ./identity.public:/var/lib/zerotier-one/identity.public:ro\n      - ./identity.secret:/var/lib/zerotier-one/identity.secret:ro\n      - ./Caddyfile:/etc/caddy/Caddyfile:ro\n  app:\n    image: ...\n```\n\n### Nomad\n\n```hcl\njob \"ztsc-demo\" {\n  type = \"service\"\n\n  group \"demo\" {\n\n    task \"zerotier\" {\n      driver = \"docker\"\n\n      config {\n        image   = \"intelecy/ztsc:latest\"\n        devices = [\n          {\n            host_path      = \"/dev/net/tun\",\n            container_path = \"/dev/net/tun\",\n          },\n        ]\n        cap_add = [\n          \"NET_ADMIN\",\n          \"SYS_ADMIN\",\n        ]\n        volumes = [\n          \"Caddyfile:/etc/caddy/Caddyfile:ro\",\n        ]\n      }\n\n      env {\n        // replace with actual values\n        ZT_NETWORK_ID      = \"001122334455667788\"\n        ZT_IDENTITY_PUBLIC = \"0123456789:0:xxx\"\n        ZT_IDENTITY_SECRET = \"0123456789:0:xxx:yyy\"\n      }\n\n      template {\n        data = \u003c\u003cEOF\nhttp://\nreverse_proxy {$NOMAD_ADDR_app_http}\nEOF\n\n        destination = \"Caddyfile\"\n      }\n\n      lifecycle {\n        sidecar = true\n        hook    = \"prestart\"\n      }\n    }\n\n    task \"app\" {\n      /*\n        fill out rest of task definition\n      */\n      resources {\n        network {\n          port \"http\" {}\n        }\n      }\n    }\n  }\n}\n```\n\nNote, the Nomad client must allow access to the various `cap_add` capabilities:\n\n```hcl\nplugin \"docker\" {\n  config {\n    allow_caps = [ \"NET_ADMIN\", \"SYS_ADMIN\" ]\n  }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fintelecy%2Fztsc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fintelecy%2Fztsc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fintelecy%2Fztsc/lists"}