Skip to content

Daemonless Reverse Proxy Daemonless Reverse Proxy

Description / nameInput element
Container Registry
Container Configuration Root Path
Timezone
User ID
Group ID
Daemonless Reverse Proxy Host Port
Daemonless Reverse Proxy /config Path
Daemonless Reverse Proxy /data Path

Build Status Last Commit OCI Pulls

Friendly HTTPS names for the services on your network: a managed Caddy reverse proxy with a web UI, secure by default.

Port 80
Registry ghcr.io/daemonless/reverse-proxy
Daemonless daemonless/reverse-proxy
Source daemonless/reverse-proxy
Website daemonless.io/images/reverse-proxy

Version Tags

Multi-arch manifests — resolve automatically to the right image for your platform.

Tag Description Best For
latest daemonless Appliance. Managed Caddy reverse proxy with a web UI, built from source. Most users — recommended.
Tag Description Best For
latest-amd64 daemonless Appliance. Managed Caddy reverse proxy with a web UI, built from source. Most users — recommended.
Tag Description Best For
latest-aarch64 daemonless Appliance. Managed Caddy reverse proxy with a web UI, built from source. Most users — recommended.

Before deploying, ensure your host environment is ready. See the Quick Start Guide for host setup instructions, including the security model for host vs. container privileges.

Deployment

services:
  reverse-proxy:
    image: "ghcr.io/daemonless/reverse-proxy:latest"
    container_name: reverse-proxy
    environment:
      - PUID=1000  # User ID for the admin process (Caddy stays root to bind 80 and 443)
      - PGID=1000  # Group ID for the admin process
      - TZ=UTC  # Timezone for the container
      # - RP_ADMIN_URL=  # Where you open the admin UI, e.g. https://192.168.1.5:8443. Optional: the proxy remembers the address you sign in at; this makes the first-run link exact before anyone has.
      # - RP_TRUST_PORT=  # Host port that reaches the HTTP trust page if you publish it on something other than 80. Optional.
      # - RP_DEBUG=  # Set to 1 for Caddy's full info-level log. Off by default so the log stays readable.
      # - RP_PAIRING_KEY=  # Admin only. A key (32+ random characters) an app that installed the proxy, such as fjord, uses to manage hosts; registered once as a connected app
      # - RP_PAIRING_NAME=  # Admin only. The connected app's name for RP_PAIRING_KEY. Default: Paired app
      # - RP_SETUP_TOKEN=  # Admin only. First-run setup token chosen by the installing app, so it can open setup for you. Default: a new one in the log
    volumes:
      - "/containers/reverse-proxy:/config"
      - "/containers/reverse-proxy/data:/data"
    ports:
      - "80:80"
      - "443:443"
      - "8443:8443"
    # always (not unless-stopped) so FreeBSD's podman rc.d auto-starts it at boot
    restart: always

Save as compose.yaml, then run podman-compose up -d.

Warning

Exposing ports in AppJail means that your service can be reached from remote hosts. If that is not your intention, do not expose the ports and communicate with the service using the jail's IPv4 address or hostname assigned by the virtual network.

1
2
3
4
5
6
# .env

DIRECTOR_PROJECT=reverse-proxy
PUID=1000
PGID=1000
TZ=UTC
# appjail-director.yml

options:
  - virtualnet: ':<random> default'
  - nat:
services:
  reverse-proxy:
    name: reverse_proxy
    options:
      - container: 'args:--pull'
      - expose: '80:80 proto:tcp'
      - expose: '443:443 proto:tcp'
      - expose: '8443:8443 proto:tcp'
    oci:
      user: root
      environment:
        - PUID: !ENV '${PUID}'
        - PGID: !ENV '${PGID}'
        - TZ: !ENV '${TZ}'
    volumes:
      - REVERSE_PROXY_CONFIG_PATH: /config
      - REVERSE_PROXY_DATA_PATH: /data
volumes:
  REVERSE_PROXY_CONFIG_PATH:
    device: '/containers/reverse-proxy'
  REVERSE_PROXY_DATA_PATH:
    device: '/containers/reverse-proxy/data'
1
2
3
4
5
6
7
# Makejail

ARG tag=latest

OPTION container=boot
OPTION overwrite=force
OPTION from=ghcr.io/daemonless/reverse-proxy:${tag}

Save the files above, then run appjail-director up.

Experimental

Bastille's OCI support is experimental. It requires buildah and shares the host network stack (inherit). Mount volumes with --volume HOST JAIL; without it, image-declared volumes are stored under ${bastille_volumesdir}/${jail}.

services:
  reverse-proxy:
    name: reverse-proxy
    image: "ghcr.io/daemonless/reverse-proxy:latest"
    network:
      - mode: host
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=UTC
    volumes:
      - "/containers/reverse-proxy:/config"
      - "/containers/reverse-proxy/data:/data"

Save as bastille-compose.yml, then run bastille up.

Open http://<server>:80 from another machine (the server's own browser can't use a published port).

Interactive Configuration

Parameters

Environment Variables

Variable Default Description
PUID 1000 User ID for the admin process (Caddy stays root to bind 80 and 443)
PGID 1000 Group ID for the admin process
TZ UTC Timezone for the container
RP_ADMIN_URL `` Where you open the admin UI, e.g. https://192.168.1.5:8443. Optional: the proxy remembers the address you sign in at; this makes the first-run link exact before anyone has.
RP_TRUST_PORT `` Host port that reaches the HTTP trust page if you publish it on something other than 80. Optional.
RP_DEBUG `` Set to 1 for Caddy's full info-level log. Off by default so the log stays readable.
RP_PAIRING_KEY <RP_PAIRING_KEY> Admin only. A key (32+ random characters) an app that installed the proxy, such as fjord, uses to manage hosts; registered once as a connected app
RP_PAIRING_NAME `` Admin only. The connected app's name for RP_PAIRING_KEY. Default: Paired app
RP_SETUP_TOKEN <RP_SETUP_TOKEN> Admin only. First-run setup token chosen by the installing app, so it can open setup for you. Default: a new one in the log

Volumes

Path Description
/config Admin state and secrets
/data Certificates, ACME account and the internal CA

Ports

Port Protocol Description
80 TCP HTTP: trust page and redirects to HTTPS
443 TCP HTTPS: your hosts
8443 TCP Admin UI (HTTPS)

Adding a name and opening it over HTTPS

First run

Start the container and read its log: it prints a link like https://<this machine>:8443/#token=XXXX-XXXX-XXXX-XXXX. Open it from another device on your network (the host itself cannot reach its own published ports) and follow the steps: password, the ending for your service names (for example apps.lan), one DNS record, and trusting the proxy on your devices. From then on the proxy remembers the address you sign in at, so reverse-proxy reset-password and a later first run print it; RP_ADMIN_URL makes the very first link exact too.

The admin UI uses a self-issued certificate until setup is done, so the browser warns once. After setup it is also served at https://proxy.<your ending>:8443 with a certificate your devices trust.

How it works

One container, two programs: Caddy runs as root so it can bind ports 80 and 443, and the admin runs as bsd (PUID/PGID). They talk over unix sockets inside the container, so the stack works on any network. Every host you add becomes a name under your ending, served over HTTPS and proxied to the address and port you gave. Hosts are "home network only" unless you open one to the internet. With a private ending (.lan, .home.arpa, …) the proxy is its own certificate authority, restricted to that ending; the Trust page walks each kind of device through trusting it once, with a QR code for phones.

With a real domain (home.example.com) the wizard asks where its DNS is hosted (Cloudflare, Route 53, DuckDNS, deSEC, Porkbun, Hetzner, OVH, Gandi, any RFC 2136 server or acme-dns; the right one is picked from the domain's nameservers) and for a token that can edit its records. The proxy then gets one wildcard certificate for every name under the domain from Let's Encrypt and renews it by itself, so no device needs any setup. The token is tried against the staging service first, so a wrong one never counts against Let's Encrypt's limits. Nothing is reachable from the internet unless a host is opened to it.

No domain? Pick "A free name from DuckDNS" in the wizard: sign in at duckdns.org, add a name, paste its token, and every service gets a name like radarr.you.duckdns.org with a certificate every device already trusts.

Go links: type go/radarr in a browser to open radarr.<your ending>. Any of your names works, with its path, and go/ alone lists them. It takes one more record on your router (go pointing at the proxy); setup and Settings → Short links walk you through it and turn green once it works. The word is yours to change, or turn off. The first time, type http://go/radarr with the http://: Safari and Firefox otherwise search the web for it.

Scripts and other apps

Anything that can make an HTTPS request can manage hosts. Make a key under Settings → Connected apps (or hand one to the proxy at install as RP_PAIRING_KEY) and send it as a bearer token; a key manages hosts and nothing else. The reference is the proxy's own https://<proxy>:8443/api/v1/openapi.json.

1
2
3
curl -sS https://192.168.1.5:8443/api/v1/hosts \
  -H "Authorization: Bearer rp_..." \
  -d '{"name":"radarr","target":{"address":"192.168.1.20","port":7878},"access":"home"}'

Inside the container the same works from the command line, no key needed:

1
2
3
podman exec reverse-proxy reverse-proxy hosts
podman exec reverse-proxy reverse-proxy hosts add radarr 192.168.1.20:7878
podman exec reverse-proxy reverse-proxy hosts remove radarr

Operations

  • Forgot the password: run podman exec reverse-proxy reverse-proxy reset-password to print a new setup link.
  • Backup: Settings → Backup downloads one passphrase-encrypted file (hosts, settings, password, root certificate). Restore it from the welcome screen of a fresh install.
  • Keep /data: it holds the certificates Caddy issued. Losing /config loses the root certificate (and the DNS token), and every device would have to trust the proxy again; a backup avoids that.
  • Certificate trouble: the Hosts page shows a banner while a certificate is being obtained or when it failed, with the reason in plain words (token rejected, token cannot edit the zone, record did not appear, rate limit). Change the token under Settings → Secure connection.
  • Non-default HTTP port: if you publish the trust page on something other than 80, set RP_TRUST_PORT so the Trust page's commands and QR code use it.

Implementation Details

  • Architectures: amd64, aarch64
  • User: root (UID/GID set via PUID/PGID). Defaults to 1000:1000.
  • Base: Built on ghcr.io/daemonless/base (FreeBSD 15.1).

Need help? Join our Discord community.