Skip to content

matheuskshn/devtunnel-toolkit

DevTunnel Toolkit

Docker License: MIT GHCR Docker Hub

Developer-focused toolkit that extends Microsoft Dev Tunnels with local network access helpers.

It packages three services:

Service Image Purpose
DevTunnel devtunnel-toolkit Hosts or connects Microsoft Dev Tunnels
Squid devtunnel-toolkit-squid HTTP/HTTPS proxy for local network access
OpenVPN devtunnel-toolkit-openvpn Privileged TCP VPN server for routed local network access

Why

Dev Tunnels is great for exposing a local development service. This toolkit adds two common development workflows:

  • Use a proxy through the tunnel to reach internal HTTP/HTTPS/SSH-over-CONNECT endpoints.
  • Use a VPN through the tunnel to route development traffic to the network where the tunnel host is running.

The default proxy port is 3140, matching the Squid configuration used by the rhel_squid_proxy_install Ansible role.

Quickstart

Login once:

make login

Or login with GitHub:

make login-github

Confirm the container volume has a cached login:

make status

Start the toolkit attached to the logs:

make up

When running attached, stopping the command with Ctrl+C also stops the Compose services. To start in the background, use:

make up-d

By default, the tunnel hosts:

Port Service
3140 Squid proxy
53194 OpenVPN TCP server

Use a persistent tunnel ID when desired:

TUNNEL_ID=my-dev-gateway make up

Allow anonymous access only when you understand the exposure:

ALLOW_ANONYMOUS=true TUNNEL_ID=my-dev-gateway make up

Docker Compose

Login once with Microsoft/Entra ID:

docker compose run --rm devtunnel login microsoft

Or login with GitHub:

docker compose run --rm devtunnel login github

Confirm the login before starting the long-running services:

docker compose run --rm devtunnel auth-check

Start everything in the background:

docker compose up -d

Or with Make:

make up-d

This starts:

Service Local endpoint
Squid proxy 127.0.0.1:3140
OpenVPN server 127.0.0.1:53194/tcp
DevTunnel host ports 3140,53194

Follow the tunnel logs:

docker compose logs -f devtunnel

Use a persistent tunnel ID:

TUNNEL_ID=my-dev-gateway docker compose up -d

Expose only the proxy:

PORTS=3140 docker compose up -d squid devtunnel

Expose only the VPN:

PORTS=53194 docker compose up -d openvpn devtunnel

Generate an OpenVPN client profile:

docker compose run --rm openvpn client > devtunnel-toolkit.ovpn

Stop the toolkit:

docker compose down

Delete containers, networks, and toolkit volumes:

docker compose down --volumes --remove-orphans

This removes the cached DevTunnel login and OpenVPN PKI/client certificates. After this command, run login again and regenerate any .ovpn files.

Equivalent Make targets:

make reset
make recreate

make reset removes containers, networks, and volumes. make recreate removes and recreates containers while keeping volumes, so the cached DevTunnel login and OpenVPN certificates are preserved. For a full wipe, run make reset, then login again and start with make up.

Authentication

Dev Tunnels requires login to create and host tunnels. The CLI supports Microsoft/Entra ID and GitHub accounts; after login, Microsoft documents that the token is cached in the system secure key chain for several days in the Dev tunnels CLI reference.

This project mounts /home/devtunnel as a named Docker volume so normal docker compose stop, docker compose restart, docker compose down, and make up runs can reuse the cached login. Commands that remove volumes, such as make reset or docker compose down --volumes, remove that cache.

make up runs an auth check before starting the tunnel host. If it says Not logged in, run:

make login-github
make status
make up

docker compose up also starts devtunnel-renew, a lightweight sidecar that shares the DevTunnel home volume. For GitHub logins, it reads the cached access token expiration and runs a DevTunnel management check shortly after expiration so the CLI can refresh and rotate the cached GitHub token pair before an idle host has to reconnect with old credentials. It does not make credentials permanent; if GitHub or Microsoft revoke a token, or if the refresh token expires, run the login flow again.

Run one renew probe manually with:

make renew

For unattended runs, provide an access token instead of a cached interactive login:

TUNNEL_ID=my-dev-gateway \
DEVTUNNEL_ACCESS_TOKEN=... \
docker compose up -d

The token is passed to devtunnel host/connect --access-token. Treat it as a secret; environment variables can be visible through Docker inspection and shell history.

Client workflow

On the client machine, run the DevTunnel client:

docker run --rm -it \
  -v devtunnel-home:/home/devtunnel \
  ghcr.io/matheuskshn/devtunnel-toolkit:edge login microsoft

docker run --rm -it \
  -v devtunnel-home:/home/devtunnel \
  ghcr.io/matheuskshn/devtunnel-toolkit:edge connect my-dev-gateway

After connect, the tunnel ports are available on the client as local ports.

Use the proxy

Configure a terminal or tool to use:

export http_proxy=http://127.0.0.1:3140
export https_proxy=http://127.0.0.1:3140
export no_proxy=localhost,127.0.0.1

Then use internal HTTP/HTTPS endpoints normally.

The Squid defaults are based on the Ansible role:

Setting Default
Listen address 127.0.0.1
HTTP port 3140
SSL ports 443 563 22
Safe ports 80 21 22 443 70 210 1025-65535 280 488 591 777

Use the VPN

Generate a client profile on the server side:

make ovpn-client > devtunnel-toolkit.ovpn

The generated profile defaults to:

remote 127.0.0.1 53194
proto tcp-client

That means the OpenVPN client connects to the local forwarded port created by devtunnel connect.

Configuration

DevTunnel

Variable Default Description
PORTS 3140,53194 Comma-separated ports hosted by Dev Tunnels
TUNNEL_ID empty Existing tunnel ID to host or connect
DEVTUNNEL_ACCESS_TOKEN empty Optional token passed as --access-token to host and connect
ALLOW_ANONYMOUS false Adds --allow-anonymous when true
PROTOCOL empty Optional http, https, or auto
EXPIRATION empty Optional tunnel expiration, such as 2h or 7d
VERBOSE false Adds --verbose when true
LOGIN_PROVIDER microsoft Provider used by bare login
DEVTUNNEL_RENEW_AFTER_EXPIRATION_SECONDS 60 Seconds after cached GitHub access-token expiration before devtunnel-renew probes the tunnel
DEVTUNNEL_RENEW_FALLBACK_INTERVAL_SECONDS 29700 Fallback renew interval when GitHub token expiration cannot be read
DEVTUNNEL_RENEW_RETRY_SECONDS 300 Retry delay after a failed renew probe
DEVTUNNEL_RENEW_MIN_SLEEP_SECONDS 30 Minimum sleep when the next renew time is very close
DEVTUNNEL_DNS_PRIMARY 1.1.1.1 First DNS server used by the DevTunnel container
DEVTUNNEL_DNS_SECONDARY 8.8.8.8 Second DNS server used by the DevTunnel container
DEVTUNNEL_DNS_FALLBACK 127.0.0.1 Fallback to the host resolver when external DNS is unavailable

The DevTunnel container uses external DNS by default because some local resolvers do not resolve Microsoft Dev Tunnels service domains. To disable the external DNS override and use only the system resolver, set:

COMPOSE_FILE=compose.yml:compose.system-dns.yml

CA certificates

The host CA bundle is mounted read-only into the DevTunnel, Squid, and OpenVPN containers so internal TLS endpoints can use locally trusted corporate CAs.

Variable Default Description
LOCAL_CA_BUNDLE /etc/ssl/certs/ca-certificates.crt Host CA bundle path; common on Debian/Ubuntu hosts
CONTAINER_CA_BUNDLE /etc/ssl/certs/ca-certificates.crt CA bundle path inside the Debian-based toolkit images

On RHEL, Rocky, Fedora, and similar hosts, set:

LOCAL_CA_BUNDLE=/etc/pki/tls/certs/ca-bundle.crt

Squid

Variable Default Description
SQUID_HTTP_PORT 3140 Squid listen port inside the container
SQUID_LISTEN_ADDRESS 127.0.0.1 Squid listen address inside the container
SQUID_VISIBLE_HOSTNAME devtunnel-toolkit-squid Squid visible hostname
SQUID_SSL_PORTS 443 563 22 Ports allowed for CONNECT
SQUID_SAFE_PORTS 80 21 22 443 70 210 1025-65535 280 488 591 777 Safe destination ports
SQUID_EXTRA_CONFIG empty Extra raw Squid configuration lines

OpenVPN

Variable Default Description
OVPN_PORT 53194 OpenVPN listen port inside the container
OVPN_LISTEN_ADDRESS 127.0.0.1 OpenVPN listen address
OVPN_PROTO tcp tcp or udp; TCP is recommended for Dev Tunnels
OVPN_NETWORK 10.8.0.0 VPN subnet network
OVPN_NETMASK 255.255.255.0 VPN subnet mask
OVPN_CIDR 10.8.0.0/24 VPN subnet CIDR used for NAT
OVPN_CLIENT_NAME devtunnel-toolkit Default client certificate/profile name
OVPN_REMOTE_HOST 127.0.0.1 Remote host written to generated client profiles
OVPN_REMOTE_PORT 53194 Remote port written to generated client profiles
OVPN_PUSH_ROUTES RFC1918 routes Comma-separated routes pushed to clients
OVPN_DNS empty Comma-separated DNS servers pushed to clients
OVPN_REDIRECT_GATEWAY false Push default route when true
OVPN_EXTRA_CONFIG empty Extra raw OpenVPN server configuration

Published images

docker pull ghcr.io/matheuskshn/devtunnel-toolkit:edge
docker pull ghcr.io/matheuskshn/devtunnel-toolkit-squid:edge
docker pull ghcr.io/matheuskshn/devtunnel-toolkit-openvpn:edge

Docker Hub publishing uses the same image names under matheuskshn/ when the repository secrets are configured.

Versioning

Images follow Git tags.

Git event Published tags
Pull request Build only, no push
Push to main edge, main
Tag v1.2.3 v1.2.3, 1.2.3, 1.2, 1, latest

Release a new version:

git tag v1.0.0
git push origin v1.0.0

Security notes

  • Squid and OpenVPN are bound to 127.0.0.1 on the host; Dev Tunnels exposes those local ports.
  • ALLOW_ANONYMOUS=true can expose your proxy/VPN to anyone with the tunnel URL or connection details.
  • OpenVPN runs as a privileged container because it needs /dev/net/tun, IP forwarding, and NAT rules.
  • Treat generated .ovpn files, tunnel URLs, and access tokens as secrets.
  • Review pushed VPN routes before starting the service in sensitive networks.

Development

make build
make help
docker compose config

References

About

Developer toolkit for Microsoft Dev Tunnels with proxy and VPN access

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors