-
Notifications
You must be signed in to change notification settings - Fork 0
Caddy CGI
CGI allows you to build lightweight endpoints in Bash (or any language with a shebang), without needing a full application server. It’s useful for tasks like returning dynamic JSON, invoking CLI tools, or integrating with your local filesystem.
Note
CGI is simple and powerful for many tasks, but not efficient for high-frequency workloads.
If you're OK with Caddy handling the response (200 every time), use Exec instead.
We'll use aksdb/caddy-cgi.
Make a directory for scripts:
mkdir caddy/scriptsBuild the Caddy image with the caddy-cgi module, copying our scripts into the image:
caddy/Dockerfile
FROM caddy:2-builder AS builder
RUN xcaddy build \
--with github.com/aksdb/caddy-cgi/v2
# Final lightweight image
FROM caddy:2
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
# Copy our Caddyfile into the image
COPY Caddyfile /etc/caddy/Caddyfile
# Copy our scripts into the image
COPY scripts /usr/local/cgi-binIf you want to add extras (like bash, jq, curl or a scripting language
such as Python), add a line such as:
RUN apk add --no-cache bash jq curlBuild the image:
docker compose build caddyMount the scripts directory in the Compose override file (which affects
development only):
compose.override.yaml
caddy:
volumes:
- ./caddy/scripts:/usr/local/cgi-bin:rocaddy/Caddyfile
cgi /path /usr/local/cgi-bin/myscript.sh arg1 arg2Make sure scripts are executable.
chmod +x caddy/scripts/myscript.shLastly, recreate the Caddy container:
docker compose up -d --force-recreate caddyOutput goes to:
-
stdoutgoes to the response. -
stderrgoes to Caddy's logs.
The stdout output must include headers:
caddy/scripts/myscript.sh
#!/bin/sh
echo "Content-Type: text/plain"
echo
echo 'Hello'cgi will respond with HTTP status 200 even if the script fails, and the body
will be whatever had been output up to the point of failure.
If you want script failures to respond with 500 Internal Server Error, and the details logged, use this wrapper script:
caddy/scripts/entry.sh
Click to expand
#!/bin/sh
set -euo pipefail
TMP=$(mktemp)
trap 'rm -f "$TMP"' EXIT
handle_error() {
code=$?
echo "Error $code in $1 on line $2" >&2
echo "Status: 500 Internal Server Error"
echo "Content-Type: text/plain"
echo
echo "Internal Server Error"
exit 0
}
TARGET=$1
shift
trap 'handle_error "$TARGET" $LINENO' ERR
# --- Source logic script in subshell, redirecting output ---
(
source "$TARGET" "$@"
) >"$TMP"
# --- Only reached on success ---
cat "$TMP"And update your route:
cgi /my_route /usr/local/bin/entry.sh myscript.shThe entry.sh script should be executable, but the scripts it runs don't need
to be.
chmod +x caddy/scripts/entry.shIf your myscript.sh is bash, the entry.sh should also use bash.