This repository provides reusable development ingress for multiple projects:
- Traefik reverse proxying on the shared
traefik_proxyDocker network - Smallstep Step CA certificates issued to Traefik through ACME
- a persistent Tailscale subnet router and CoreDNS split DNS for
tail.gg - sslip.io names for host-only development without tailnet DNS
- optional, self-contained service recipes
Projects keep ownership of their application containers and Traefik labels. This stack owns the cross-project edge, certificate authority, DNS server, and Tailscale connector.
docker-compose.yaml: master Compose model and stable shared networkscompose/edge/: Step CA and Traefikcompose/tailscale/: Tailscale connector, CoreDNS, and its operator guidecompose/observability/: shared operational tooling such as Dozzletraefik/traefik-static.yaml: Static Traefik configuration with ACME resolvertaskfile.yaml: CLI automation withtaskcerts/: Extracted TLS certificates, including the root CArecipes/: optional examples that are not included in the central stack
Note
Step CA uses network_mode: host to resolve 127.0.0.1 domains during ACME
challenges, while Traefik connects via host.docker.internal for certificate
requests.
First, clone this repository to a local directory where you'll be running your development environment:
git clone https://github.com/teyfix/traefik
cd traefikMake sure you're inside the cloned folder before running any of the next steps.
Set the non-secret shared network values in .env. The intended defaults are:
TAIL_DOMAIN=tail.gg
DIRECT_DOMAIN=dkr.tail.gg
TS_SERVICE_SUBNET=10.10.10.0/24
TS_DNS_SERVER=10.10.10.10
TS_ROUTES=10.10.10.0/24,172.16.0.0/12Complete the one-time tailnet policy, route-approval, DNS, and auth-key setup in
compose/tailscale/README.md. Put TS_AUTHKEY
only in the documented gitignored local environment file; never commit it.
docker compose config
task upThis will:
- Start Step CA, Traefik, and observability services
- Wait until Step CA is healthy
- Allow Traefik to request certificates using ACME
- Create stable external networks for application projects
Tailscale and CoreDNS are deliberately behind the tailscale profile. After
the tailnet administration and local auth-key setup are complete, render and
start the full stack with:
docker compose --profile tailscale config
task up:fulltask up:full starts or recreates the base dependencies as well as Tailscale
and CoreDNS. The equivalent raw start command is
docker compose --profile tailscale up -d.
task certs:installThis will:
- Copy
root_ca.crtfrom the Step CA container - Install it to your system trust store via
update-ca-certificates
β Works for WSL, Debian, Ubuntu, etc.
Tip
You can use /usr/local/share/ca-certificates/traefik-stepca-root-ca.crt as
the root certificate for apps that do not use the system trust store.
To make Windows trust the locally issued TLS certificates:
task certs
explorer.exe certsThen follow these steps to install the certificate:
- In the opened folder, double-click the file named
root_ca.crt. - A security warning will appear β click "Open".
- The certificate viewer will open. Click "Install Certificate...".
- Choose "Local Machine" (this requires administrator privileges), then click Next.
- Select "Place all certificates in the following store", then click Browse.
- Choose "Trusted Root Certification Authorities", then click OK.
- Click Next, then Finish.
- A final prompt will confirm the installation β click Yes.
π‘οΈ You should now be able to visit services like
https://traefik.127-0-0-1.sslip.ioin your browser without any certificate warnings.
For the normal HTTPS path, attach a service to traefik_proxy and keep its
project-owned Traefik labels. A host such as api.ukiyo.tail.gg resolves to
Traefik, which then selects the project router.
Direct container access is opt-in and uses an exact alias such as
hello.ukiyo.dkr.tail.gg on the external tailscale_services network. It
bypasses Traefik security and TLS. See the
tailscale-direct recipe before using
that path.
You can run services like PostgreSQL behind Traefik using TCP with TLS termination:
networks:
traefik_proxy:
name: traefik_proxy
external: true
services:
postgres:
image: teyfix/timescaledb-pgrx:latest
labels:
- "traefik.enable=true"
- "traefik.tcp.routers.teyfix_pg.rule=HostSNI(`pg.teyfix.127-0-0-1.sslip.io`)"
- "traefik.tcp.routers.teyfix_pg.entrypoints=shared"
- "traefik.tcp.routers.teyfix_pg.service=teyfix_pg"
- "traefik.tcp.routers.teyfix_pg.tls=true"
- "traefik.tcp.routers.teyfix_pg.tls.certresolver=stepca"
- "traefik.tcp.services.teyfix_pg.loadbalancer.server.port=5432"
networks:
- traefik_proxyYou can now securely connect to PostgreSQL at
pg.teyfix.127-0-0-1.sslip.io:4040 with TLS.
Note
Port 4040 corresponds to the shared TCP entrypoint defined in Traefik's
configuration, which is designed for non-HTTP services like databases.
You can also expose standard HTTP services like MinIO behind Traefik with HTTPS:
networks:
traefik_proxy:
name: traefik_proxy
external: true
services:
minio:
image: minio/minio:latest
environment:
# Prevents redirecting to the console when accessing the API directly
- MINIO_BROWSER_REDIRECT=false
expose:
- 9000 # API
- 9001 # Console
networks:
- traefik_proxy
labels:
- "traefik.enable=true"
# MinIO API
- "traefik.http.routers.teyfix_minio_api.rule=Host(`minio-api.teyfix.127-0-0-1.sslip.io`)"
- "traefik.http.routers.teyfix_minio_api.tls=true"
- "traefik.http.routers.teyfix_minio_api.entrypoints=websecure"
- "traefik.http.routers.teyfix_minio_api.tls.certresolver=stepca"
- "traefik.http.routers.teyfix_minio_api.service=teyfix_minio_api"
- "traefik.http.services.teyfix_minio_api.loadbalancer.server.port=9000"
# MinIO Console
- "traefik.http.routers.teyfix_minio_console.rule=Host(`minio-console.teyfix.127-0-0-1.sslip.io`)"
- "traefik.http.routers.teyfix_minio_console.tls=true"
- "traefik.http.routers.teyfix_minio_console.entrypoints=websecure"
- "traefik.http.routers.teyfix_minio_console.tls.certresolver=stepca"
- "traefik.http.routers.teyfix_minio_console.service=teyfix_minio_console"
- "traefik.http.services.teyfix_minio_console.loadbalancer.server.port=9001"β Once running, you can securely access:
https://minio-api.teyfix.127-0-0-1.sslip.iofor the APIhttps://minio-console.teyfix.127-0-0-1.sslip.iofor the web console
| Task | Description |
|---|---|
task up |
Start/recreate base services without stopping Tailscale |
task up:full |
Start/recreate base, Tailscale, and CoreDNS services |
task down |
Stop both profiles while preserving persistent state |
task recreate |
Recreate base services without stopping Tailscale |
task recreate:full |
Stop and recreate both profiles |
task logs |
Follow logs of all containers |
task certs |
Export certificates from Step CA |
task certs:install |
Install the root CA into your Linux trust store |
task purge |
Stop both profiles and remove CA, ACME, and Tailscale state |
Once up, you can access the Traefik dashboard via either:
- HTTPS (recommended):
https://traefik.127-0-0-1.sslip.io - HTTP (insecure):
http://localhost:8080
Tip
The HTTPS version uses certificates issued by your local Step CA, while the HTTP version runs in insecure mode for development convenience.
If you need direct access to Step CA for advanced certificate management:
https://localhost:9000Note
Direct Step CA access is typically not needed for normal development workflows, as Traefik handles certificate requests automatically via ACME.
- Root CA is generated by Step CA and used by Traefik's ACME resolver
- Certificates are stored under
/home/step/certs/in thestepcacontainer - Traefik mounts these and uses them via
certResolver: stepca
This stack exposes application services through two distinct paths:
- Ordinary names such as
api.ukiyo.tail.ggresolve through CoreDNS at10.10.10.10to Traefik's Docker address. The project service only needs thetraefik_proxynetwork and its own labels. - Names under
dkr.tail.gg, such ashello.ukiyo.dkr.tail.gg, resolve through Docker embedded DNS to the exact alias of a container explicitly joined totailscale_services.
Tailscale routes the returned IP, not the hostname. CoreDNS selects the
destination address class. The subnet router advertises 10.10.10.0/24 for
CoreDNS/direct containers and 172.16.0.0/12 for Docker/Traefik.
Traefik publishes ports 80, 443, 8080, and 4040 only on
127.0.0.1. Host-local clients can still use those published ports, while
ordinary LAN clients cannot reach them through a host interface. Tailnet
clients instead reach Traefik's Docker address through the approved
172.16.0.0/12 subnet route. This boundary depends on restrictive Tailscale
grants: private tail.gg DNS names are service discovery, not authorization.
Certificate validation additionally uses this configuration:
- Step CA runs in
network_mode: hostto properly resolve127.0.0.1domains during ACME challenges - Traefik connects to Step CA via
host.docker.internal:9000for certificate requests - Services run on the
traefik_proxybridge network for proper service discovery
Important
Step CA cannot access Traefik services for ACME validation when both are on
Docker bridge networks due to 127.0.0.1 resolution limitations. The host
networking mode for Step CA resolves this issue.
The complete tailnet policy, split-DNS, route, ownership, direct-container, and
migration contract is documented in
compose/tailscale/README.md.
- This setup is for local/dev use only
- Certificates are not publicly trusted
- Browsers may still show a warning unless root CA is manually trusted
- Host-published Traefik ports are loopback-only; tailnet access uses the routed Docker address and must be restricted with Tailscale grants
- Private
tail.ggDNS records do not authorize access or replace application authentication for sensitive services - Direct
*.dkr.tail.ggexposure bypasses Traefik TLS, middleware, and auth 172.16.0.0/12routing can overlap client LAN, VPN, or Docker networks- Tailnet split DNS for
tail.ggshadows public records under the same suffix
Using an owned suffix provides stable OAuth callback names, but providers such as Google still require the configured domain, HTTPS rules, and redirect URI to match exactly. Some providers require public reachability or a publicly trusted certificate; trusting the private Step CA locally does not satisfy those checks.
task downThis stops the stack and removes orphan containers while preserving persistent
Step CA, ACME, and Tailscale state. To deliberately delete that state, use
task purge and confirm the destructive prompt.
- Docker
- Docker Compose with support for top-level
include - Task
- Linux or WSL (for root CA trust automation)
Warning
This image is outdated and will be updated in the future.





