Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ on:

jobs:
build:
uses: daemonless/dbuild/.github/workflows/daemonless-build.yaml@main
# Trial of dbuild#feat/linux-images: back to @main (and no dbuild_ref)
# once that is merged.
uses: daemonless/dbuild/.github/workflows/daemonless-build.yaml@feat/linux-images
with:
image_name: reverse-proxy
dbuild_ref: feat/linux-images
linux: true
secrets: inherit
68 changes: 68 additions & 0 deletions Containerfile.linux
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# reverse-proxy on Linux, built only by dbuild's shared GitHub workflow
# (build-linux job): the same Caddy with the same DNS modules, the same
# admin and UI, on Alpine with s6. Caddy runs as root to bind 80/443; the
# admin runs as bsd (PUID/PGID), as on FreeBSD. Keep APP_VER with
# Containerfile.j2's: the manifest adds this image to a tag only when the
# versions agree.
ARG APP_VER=0.2.0
ARG CADDY_VERSION=

FROM golang:1.26-alpine AS caddy-build
ARG CADDY_VERSION
RUN apk add --no-cache git ca-certificates curl && \
go install github.com/caddyserver/xcaddy/cmd/xcaddy@v0.4.7
RUN v="${CADDY_VERSION:-$(curl -fsSI https://github.com/caddyserver/caddy/releases/latest | grep -io 'releases/tag/v[^[:space:]]*' | head -1 | cut -d/ -f3)}" && \
test -n "$v" && \
CGO_ENABLED=0 GOFLAGS=-buildvcs=false xcaddy build "$v" \
--with github.com/caddy-dns/cloudflare \
--with github.com/caddy-dns/route53 \
--with github.com/caddy-dns/desec \
--with github.com/caddy-dns/porkbun \
--with github.com/caddy-dns/hetzner \
--with github.com/caddy-dns/ovh \
--with github.com/caddy-dns/gandi \
--with github.com/caddy-dns/rfc2136 \
--with github.com/caddy-dns/acmedns \
--with github.com/caddy-dns/duckdns@v0.5.0 \
--output /out/caddy && \
/out/caddy version

FROM node:24-alpine AS ui-build
COPY admin/ui/package.json admin/ui/package-lock.json /src/ui/
RUN cd /src/ui && npm ci --no-fund --no-audit
COPY admin/ui/ /src/ui/
RUN cd /src/ui && npm run build

FROM golang:1.26-alpine AS admin-build
ARG APP_VER
COPY admin/ /src/
COPY --from=ui-build /src/ui/dist/ /src/ui/dist/
RUN cd /src && CGO_ENABLED=0 go build -trimpath \
-ldflags "-s -w -X main.version=${APP_VER}" \
-o /out/reverse-proxy ./cmd/reverse-proxy

FROM alpine:3.24
ARG APP_VER
LABEL org.opencontainers.image.title="Daemonless Reverse Proxy" \
org.opencontainers.image.description="Friendly HTTPS names for the services on your network: a managed Caddy reverse proxy with a web UI, secure by default." \
org.opencontainers.image.source="https://github.com/daemonless/reverse-proxy" \
org.opencontainers.image.url="https://daemonless.io/images/reverse-proxy/" \
org.opencontainers.image.licenses="BSD-2-Clause" \
org.opencontainers.image.vendor="daemonless" \
org.opencontainers.image.authors="daemonless" \
io.daemonless.category="Network" \
io.daemonless.port="8443" \
io.daemonless.volumes="/config,/data"
RUN apk add --no-cache s6 ca-certificates tzdata curl && \
addgroup -g 1000 bsd && adduser -D -H -u 1000 -G bsd -s /sbin/nologin bsd && \
mkdir -p /app /config /data && echo "${APP_VER}" > /app/version
COPY --from=caddy-build /out/caddy /usr/local/bin/caddy
COPY --from=admin-build /out/reverse-proxy /usr/local/bin/reverse-proxy
COPY --from=ui-build /src/ui/dist/ /usr/local/share/reverse-proxy/ui/
COPY root/etc/reverse-proxy/bootstrap.json /etc/reverse-proxy/bootstrap.json
COPY root-linux/ /
RUN chmod +x /init /healthz /usr/local/bin/s6-ready-when /etc/cont-init.d/* /etc/services.d/*/run
EXPOSE 80 443 8443
VOLUME ["/config", "/data"]
HEALTHCHECK --interval=30s --timeout=10s CMD /healthz
ENTRYPOINT ["/init"]
7 changes: 7 additions & 0 deletions root-linux/etc/cont-init.d/10-users
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/sh
# PUID/PGID mapping for the admin, as the FreeBSD image does.
PUID="${PUID:-1000}"; PGID="${PGID:-1000}"
if [ "$(id -g bsd)" != "$PGID" ]; then delgroup bsd 2>/dev/null || true; addgroup -g "$PGID" bsd; fi
if [ "$(id -u bsd)" != "$PUID" ]; then deluser bsd 2>/dev/null || true; adduser -D -H -u "$PUID" -G bsd -s /sbin/nologin bsd; fi
echo "[usermod] User bsd configured (uid=$(id -u bsd), gid=$(id -g bsd))"
mkdir -p /config/logs && chown -R bsd:bsd /config
17 changes: 17 additions & 0 deletions root-linux/etc/cont-init.d/20-reverse-proxy-dirs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/sh
# Socket directory: root-owned, group bsd. setgid so the sockets Caddy (root)
# makes get the group bsd: FreeBSD does that by default, Linux needs the bit.
# Nothing else runs here, so a socket left from an unclean stop is stale.
rm -rf /var/run/reverse-proxy
mkdir -p /var/run/reverse-proxy
chown root:bsd /var/run/reverse-proxy
chmod 2770 /var/run/reverse-proxy

mkdir -p /config/secrets
chown bsd:bsd /config/secrets
chmod 0700 /config/secrets

# Caddy (root) owns /data: certificates, ACME account, CA intermediate.
mkdir -p /data
chown root:root /data
chmod 0700 /data
1 change: 1 addition & 0 deletions root-linux/etc/services.d/admin/notification-fd
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3
10 changes: 10 additions & 0 deletions root-linux/etc/services.d/admin/run
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/sh
# The admin: UI, API, config renderer. Runs as bsd (PUID/PGID) and drives
# Caddy over the unix sockets in /var/run/reverse-proxy.
export HOME=/config
s6-ready-when /healthz
exec s6-setuidgid bsd /usr/local/bin/reverse-proxy \
-state /config \
-caddy-socket /var/run/reverse-proxy/caddy.sock \
-listen /var/run/reverse-proxy/admin.sock \
-probe-socket /var/run/reverse-proxy/tls.sock 2>&1
1 change: 1 addition & 0 deletions root-linux/etc/services.d/caddy/notification-fd
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3
9 changes: 9 additions & 0 deletions root-linux/etc/services.d/caddy/run
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/sh
export XDG_DATA_HOME=/data
export XDG_CONFIG_HOME=/data
SOCK=/var/run/reverse-proxy/caddy.sock
# Linux gives a new unix socket the creator's group, not the directory's
# (FreeBSD does the latter), so hand it to bsd once Caddy has made it.
( i=0; while [ ! -S "$SOCK" ] && [ $i -lt 300 ]; do sleep 0.2; i=$((i+1)); done; chgrp bsd "$SOCK" 2>/dev/null ) &
s6-ready-when test -S "$SOCK"
exec /usr/local/bin/caddy run --config /etc/reverse-proxy/bootstrap.json 2>&1
5 changes: 5 additions & 0 deletions root-linux/healthz
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/sh
# As on FreeBSD: the admin answers on its socket, and answers 503 while Caddy
# is not running. Asked directly, not through Caddy on :8443: a check before
# the admin listened made Caddy log "admin.sock: no such file" at every boot.
curl -sf -m 5 -o /dev/null --unix-socket /var/run/reverse-proxy/admin.sock http://admin/healthz
10 changes: 10 additions & 0 deletions root-linux/init
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/sh
# Minimal init: run the one-shot cont-init scripts, then supervise the two
# services with s6 (same layout as the FreeBSD image).
set -e
for f in /etc/cont-init.d/*; do
echo "[init] Running: $f"
"$f"
done
echo "[init] Initialization complete"
exec s6-svscan /etc/services.d
17 changes: 17 additions & 0 deletions root-linux/usr/local/bin/s6-ready-when
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/sh
# Poll a command until it succeeds, then signal s6 readiness on fd 3
# (needs notification-fd = 3 in the service directory). Bounded, and dies
# with its service.
[ -n "$1" ] || { echo "Usage: s6-ready-when <command>"; exit 1; }
CHECK_CMD="$*"
SERVICE_PID=$PPID
(
tries="${S6_READY_TIMEOUT:-300}"
while ! eval "$CHECK_CMD" >/dev/null 2>&1; do
kill -0 "$SERVICE_PID" 2>/dev/null || exit 0
tries=$((tries - 1)); [ "$tries" -gt 0 ] || exit 0
sleep 1
done
kill -0 "$SERVICE_PID" 2>/dev/null || exit 0
printf "\n" >&3
) &
Loading