Portainer Templates logo

Portainer Templates

DockRoute DockRoute

Container

DNSNetworkingTools

External-DNS for plain Docker hosts: watches running containers, reads dockroute. labels and reconciles the matching DNS records — and Cloudflare Tunnel routes — in a pluggable provider. TXT-based ownership means it never alters records it cannot prove it manages. Source: https://github.com/Dockroute/Dockroute

Image details

Architecture: amd64, arm64
Image size: 103 MB
User: dockroute

Source details

Stars: 16
Forks: 1
Language: TypeScript
License: MIT
Updated: 3 days ago

Configuration

Type
Container
Platform
linux
Image
ghcr.io/dockroute/dockroute:latest
Volumes
/var/run/docker.sock : /var/run/docker.sock
Env vars
DOCKROUTE_PROVIDER=logDOCKROUTE_OWNER_ID=defaultDOCKROUTE_POLICY=syncDOCKROUTE_DEFAULT_TARGET=DOCKROUTE_DOMAIN_FILTER=DOCKROUTE_RESYNC_SECONDS=60CLOUDFLARE_API_TOKEN=CLOUDFLARE_ACCOUNT_ID=CLOUDFLARE_TUNNEL_ID=
Restart
unless-stopped

Notes

Deploys with zero configuration: the entrypoint grants the app user the Docker socket's group and drops privileges before starting, and the default 'log' provider is a dry run that needs no credentials. Once the output looks right, switch DOCKROUTE_PROVIDER to cloudflare and set CLOUDFLARE_API_TOKEN. Opt containers in with the labels dockroute.enabled=true and dockroute.hostname=app.example.com. Docs: https://www.dockroute.dev

Standalone Install

Select an install method, to see config/commands for deploying DockRoute

Installation method

Install on Portainer

Import all app templates into your Portainer instance, for easy 1-click deploys

  1. Ensure both Docker and Portainer are installed, and up-to-date
  2. Log into your Portainer web UI
  3. Under Settings → App Templates, paste the below URL
  4. Head to Home → App Templates, and the list of apps will show up
  5. Select DockRoute, fill in any config options, and hit Deploy

Template Import URL

https://raw.githubusercontent.com/Lissy93/portainer-templates/main/templates.json
Show Me demo

More install options in our documentation, or see Dockroute/Dockroute for app-specific guidance.

dockroute

CI CodeQL codecov Release License: MIT
External-DNS for plain Docker hosts: dockroute watches your running containers, reads dockroute.* labels and reconciles the matching DNS records — and Cloudflare Tunnel routes — in a pluggable provider.
Your Docker Compose file is the source of truth; dockroute makes the provider match it, and never alters what it cannot prove it manages (ExternalDNS-style TXT ownership).
Status: MVP + Cloudflare. DNS records, TXT ownership, sync policies and Cloudflare Tunnel routes work end to end. See ARCHITECTURE.md.

Quick start

Label a container:
services:
  whoami:
    image: traefik/whoami
    labels:
      dockroute.enabled: "true"
      dockroute.hostname: "whoami.example.com"
      # publish through an existing Cloudflare Tunnel:
      dockroute.tunnel.service: "http://whoami:80"

Run dockroute next to it:
services:
  dockroute:
    image: ghcr.io/dockroute/dockroute:latest
    environment:
      DOCKROUTE_PROVIDER: cloudflare
      DOCKROUTE_OWNER_ID: home-lab
      CLOUDFLARE_API_TOKEN: ${CLOUDFLARE_API_TOKEN}
      # only needed for tunnel publishing:
      CLOUDFLARE_ACCOUNT_ID: ${CLOUDFLARE_ACCOUNT_ID}
      CLOUDFLARE_TUNNEL_ID: ${CLOUDFLARE_TUNNEL_ID}
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    # No user/group setup needed: the entrypoint detects the socket's group,
    # grants it to the app user and drops privileges before starting.

Or start with the zero-credential dry run:
bun install
DOCKROUTE_DEFAULT_TARGET=192.168.1.10 bun start   # provider=log by default

Labels

LabelRequiredDefaultDescription
dockroute.enabledyestrue opts the container in
dockroute.hostnameyesFQDN(s), comma-separated
dockroute.typenoAA, AAAA or CNAME
dockroute.targetnoDOCKROUTEDEFAULTTARGETRecord value (IP or CNAME target)
dockroute.ttlno300TTL in seconds
dockroute.tunnel.servicenoOrigin URL; publish via Cloudflare Tunnel instead of a plain record
dockroute.cloudflare.proxiednofalseProxy plain records through Cloudflare

Configuration

VariableDefaultDescription
DOCKERSOCK/var/run/docker.sockDocker Engine socket path
DOCKROUTEPROVIDERloglog (dry-run) or cloudflare
DOCKROUTEDEFAULTTARGETFallback target when label is omitted
DOCKROUTERESYNCSECONDS60Interval of the periodic full reconcile
DOCKROUTEOWNERIDdefaultOwnership id — lets several instances share a zone safely
DOCKROUTEPOLICYsyncsync, upsert-only or create-only
DOCKROUTETXTPREFIXdockroute-Ownership TXT name prefix
DOCKROUTEDOMAINFILTERComma-separated zone allowlist
CLOUDFLAREAPITOKENToken with Zone→DNS→Edit (+ Account→Cloudflare Tunnel→Edit for tunnels)
CLOUDFLAREACCOUNTIDFor tunnel publishing
CLOUDFLARETUNNELIDFor tunnel publishing (existing tunnel, you run cloudflared)

Safety model

  • Every record dockroute creates gets a companion TXT record
(_dockroute-a.whoami.example.com) carrying its owner id.
  • Records without that proof of ownership are never modified, deleted or
adopted — conflicts are logged and skipped.
  • Orphan cleanup (container gone → records removed) only happens under the
default sync policy and only for records this instance owns.
  • Tunnel ingress rules that dockroute did not create are preserved verbatim;
dockroute assumes it is the only automated writer for the tunnels it manages.

Development

bun install
bun test            # unit tests (in-memory fakes, no real HTTP)
bun run typecheck   # strict TypeScript
bun run lint        # Biome — bun run lint:fix to auto-fix

See CONTRIBUTING.md for the ground rules (ownership safety, anti-corruption layer, testing style) and how to add a provider.

License

MIT

Check the logs first

Nine times out of ten the logs tell you exactly what went wrong.

  • In Portainer, go to Containers, click the container, then Logs. Or run docker logs dockroute
  • Exit codes help too: 137 means killed, usually out of memory. 126 or 127 means the command inside the image is broken.

Can't reach the Docker socket

dockroute talks to Docker through /var/run/docker.sock. If the logs show "permission denied while trying to connect to the Docker daemon socket", the app's user can't access it.

  • Check the socket exists on the host: ls -l /var/run/docker.sock
  • Run the container as root, or add the docker group's id to the container with group_add.

Image won't pull

Test the pull directly on the host: docker pull ghcr.io/dockroute/dockroute:latest

  • "manifest unknown" means the tag no longer exists. This template uses latest, so try pinning a specific version instead.
  • "toomanyrequests" is the Docker Hub rate limit. Log in with docker login to raise it.
  • "no space left on device" means a full disk. Reclaim space with docker system prune

"exec format error"

This means the image was built for a different CPU architecture than your server.

  • This image supports: amd64, arm64
  • Check yours with uname -m: x86_64 is amd64, aarch64 is arm64. Raspberry Pi and other ARM boards are the usual culprits.

Container keeps restarting

The unless-stopped restart policy relaunches the app after every crash, so the real error can scroll past.

  • Check the logs right after a restart, the last few lines before it died are the useful ones.
  • Get the exit code with docker inspect dockroute --format '{{.State.ExitCode}}'
  • Still stuck? Redeploy once with the restart policy set to no so the failure stays visible.

Required settings are blank

DOCKROUTE_DEFAULT_TARGET, DOCKROUTE_DOMAIN_FILTER, CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_TUNNEL_ID have no default value, and dockroute may crash or misbehave if left empty.

  • Fill them in on the deploy screen before hitting deploy.

Raise an issue

Found something which isn't working as it should? Here's how to report it.

A single container

DockRoute runs as one container, the simplest kind of app here. Just the one image to pull and nothing else wired up alongside it.

The app image

An image is the app packed up ready to go, everything DockRoute needs bundled into one download. This template pulls ghcr.io/dockroute/dockroute:latest, which Docker fetches once (about 103 MB) and then starts your own copy from.

Where the image comes from

Docker pulls its images from registries, public libraries of ready-built apps. DockRoute's comes from the GitHub Container Registry, published by dockroute.

Version tags

The bit after the colon in the image name is the version tag. Here it's latest, which always points at the newest build, so a redeploy can bump you to a newer release without you asking. Pin a specific tag if you would rather stay on one version.

Which machines it runs on

Every image is built for particular CPU types. This one ships for amd64, arm64, so it runs on both regular x86 servers and ARM boards like a Raspberry Pi.

Volumes

A volume is where DockRoute keeps its files so they survive an update or a restart. Without one, anything it saves would sit inside the container and vanish the moment it's recreated. This template mounts:

  • /var/run/docker.sock from /var/run/docker.sock on the host, read-only (a socket it talks to, not storage)

Environment variables

Environment variables are the settings you hand over when you deploy, things like a password or a timezone. DockRoute takes 9 of them, and 5 need a value before it'll start properly:

  • DOCKROUTE_PROVIDER, pick one of log, cloudflare. DNS provider ('log' is a credential-free dry run that only prints the desired state)
  • DOCKROUTE_OWNER_ID, defaults to default. Ownership id written to companion TXT records (lets several instances share a zone safely)
  • DOCKROUTE_POLICY, pick one of sync, upsert-only, create-only. Sync policy — sync creates/updates/deletes owned records, upsert-only never deletes, create-only never updates or deletes
  • DOCKROUTE_DEFAULT_TARGET, needs a value. Default target — fallback record value (IP or CNAME target) when a container omits the dockroute.target label
  • DOCKROUTE_DOMAIN_FILTER, needs a value. Domain filter — comma-separated allowlist of DNS zones DockRoute may touch (blank = every zone the token can see)
  • DOCKROUTE_RESYNC_SECONDS, defaults to 60. Resync interval in seconds (periodic full reconcile that backs up the event stream)
  • CLOUDFLARE_API_TOKEN, needs a value. Cloudflare API token — required when provider is cloudflare (Zone → DNS → Edit)
  • CLOUDFLARE_ACCOUNT_ID, needs a value. Cloudflare account ID (only needed for Cloudflare Tunnel publishing)
  • CLOUDFLARE_TUNNEL_ID, needs a value. Cloudflare tunnel ID — an existing tunnel; you keep running cloudflared yourself (only needed for Tunnel publishing)

Restart policy

The restart policy here is unless-stopped, so Docker restarts DockRoute after a crash or reboot, but leaves it off when you stop it on purpose. You can change this on the deploy screen. The choices are no (never restart), on-failure (only after a crash), unless-stopped (restart unless you stop it), and always (bring it back no matter what).

Networking

Nothing custom is set, so DockRoute sits on Docker's default bridge network: its own private space that reaches the outside world only through the ports it publishes.

Container name

Once it's deployed, Portainer names the container dockroute. That's what you'll spot in the containers list and use in commands like docker logs dockroute.

Platform

The platform is linux, the kind of system the container is built to run on. Docker and Portainer handle this on a normal Linux server.

Open source license

DockRoute is open source, released under the MIT license. In plain terms the code is out in the open, so you're free to run it and change it to fit what you need.

Portainer app templates

Zooming out, this whole page comes from a Portainer app template: a short recipe telling Portainer how to set DockRoute up. Add the template list to Portainer once, then deploying DockRoute is a click rather than a wall of config.