docker: run as non-root, add HEALTHCHECK, document upaas deploy (closes #147)
check / check (push) Failing after 1s
check / check (push) Failing after 1s
The image had never been run. A trial run (fresh named volume, port 8080, real targets, no notification endpoints) showed it exited at once: Viper searches the working directory and, with a YAML config type, also matches an extension-less file named dnswatcher, so the binary at /app/dnswatcher was parsed as a config file. The binary now lives in /usr/local/bin and the working directory is the data dir. The runtime stage also gains an unprivileged dnswatcher user (uid 10001) that owns /var/lib/dnswatcher, so a fresh named volume inherits writable ownership, and a Docker HEALTHCHECK that probes /.well-known/healthcheck with busybox wget. README gains a "Deploying with upaas" section. Model: opus-4-8
This commit is contained in:
+22
-6
@@ -41,15 +41,31 @@ FROM alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4
|
|||||||
|
|
||||||
RUN apk add --no-cache ca-certificates tzdata
|
RUN apk add --no-cache ca-certificates tzdata
|
||||||
|
|
||||||
WORKDIR /app
|
# The binary lives in /usr/local/bin, not the working directory: config
|
||||||
|
# loading searches the working directory, and with a YAML config type Viper
|
||||||
|
# also matches an extension-less file named "dnswatcher" there, so a binary
|
||||||
|
# named "dnswatcher" in that directory would be parsed as a config file.
|
||||||
|
COPY --from=builder /src/bin/dnswatcher /usr/local/bin/dnswatcher
|
||||||
|
|
||||||
COPY --from=builder /src/bin/dnswatcher /app/dnswatcher
|
# Run as an unprivileged user. The data directory is owned by that user so
|
||||||
|
# writes succeed both on a bind mount and when Docker seeds a fresh named
|
||||||
# Create data directory
|
# volume from the image (a fresh volume inherits this directory's ownership).
|
||||||
RUN mkdir -p /var/lib/dnswatcher
|
RUN addgroup -S dnswatcher \
|
||||||
|
&& adduser -S -G dnswatcher -u 10001 dnswatcher \
|
||||||
|
&& mkdir -p /var/lib/dnswatcher \
|
||||||
|
&& chown dnswatcher:dnswatcher /var/lib/dnswatcher
|
||||||
|
|
||||||
ENV DNSWATCHER_DATA_DIR=/var/lib/dnswatcher
|
ENV DNSWATCHER_DATA_DIR=/var/lib/dnswatcher
|
||||||
|
|
||||||
|
WORKDIR /var/lib/dnswatcher
|
||||||
|
|
||||||
|
USER dnswatcher
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
ENTRYPOINT ["/app/dnswatcher"]
|
# busybox wget (already in alpine) probes the health endpoint. PORT defaults
|
||||||
|
# to 8080 and is honoured if the operator overrides it.
|
||||||
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||||
|
CMD wget -q -O /dev/null "http://127.0.0.1:${PORT:-8080}/.well-known/healthcheck" || exit 1
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/local/bin/dnswatcher"]
|
||||||
|
|||||||
@@ -467,6 +467,35 @@ docker run -d \
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Deploying with upaas
|
||||||
|
|
||||||
|
upaas builds this repo's `Dockerfile` and runs the resulting image with the
|
||||||
|
volume, port, and environment variables the operator enters. Configure:
|
||||||
|
|
||||||
|
- **Branch:** `main`. This is the recommended branch on the deployment
|
||||||
|
decision issue and is not yet confirmed by the owner.
|
||||||
|
- **Volume:** mount persistent storage at `/var/lib/dnswatcher`. The image runs
|
||||||
|
as an unprivileged user (`dnswatcher`, uid 10001) that owns this directory; a
|
||||||
|
fresh named volume inherits that ownership, and a bind-mounted host directory
|
||||||
|
must be writable by uid 10001.
|
||||||
|
- **Port:** the container listens on `8080`. Override with `PORT` if needed;
|
||||||
|
the healthcheck honours it.
|
||||||
|
- **Required environment:** `DNSWATCHER_TARGETS`, a comma-separated list of the
|
||||||
|
domains and hostnames to watch. The process refuses to start without it.
|
||||||
|
- **Recommended environment:** the notification endpoints
|
||||||
|
`DNSWATCHER_SLACK_WEBHOOK`, `DNSWATCHER_MATTERMOST_WEBHOOK`, and
|
||||||
|
`DNSWATCHER_NTFY_TOPIC` (without at least one, changes are only visible on the
|
||||||
|
dashboard); and `DNSWATCHER_METRICS_USERNAME` / `DNSWATCHER_METRICS_PASSWORD`
|
||||||
|
to enable the basic-auth-protected `/metrics` endpoint.
|
||||||
|
- **Healthcheck:** the image already declares a Docker `HEALTHCHECK` against
|
||||||
|
`/.well-known/healthcheck`; no operator configuration is needed. The same
|
||||||
|
path is available for an external probe.
|
||||||
|
|
||||||
|
The dashboard is unauthenticated and shows every watched name and recent alert;
|
||||||
|
decide deliberately whether to expose it publicly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Monitoring Lifecycle
|
## Monitoring Lifecycle
|
||||||
|
|
||||||
1. **Startup**: Load state from disk. If no state file exists, start
|
1. **Startup**: Load state from disk. If no state file exists, start
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ Rationale, Design, TODO, License, Author) if any are still missing.
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-09-21: upaas deploy readiness — runtime image runs as unprivileged
|
||||||
|
`dnswatcher`, Docker `HEALTHCHECK`, README "Deploying with upaas" (closes #147).
|
||||||
- 2026-09-21: `go mod tidy` dropped the redundant `golang.org/x/sync`
|
- 2026-09-21: `go mod tidy` dropped the redundant `golang.org/x/sync`
|
||||||
`// indirect` line so `script/bootstrap` leaves a clean tree (#132)
|
`// indirect` line so `script/bootstrap` leaves a clean tree (#132)
|
||||||
- 2026-08-10: comment-only corrections to `script/bootstrap`,
|
- 2026-08-10: comment-only corrections to `script/bootstrap`,
|
||||||
|
|||||||
Reference in New Issue
Block a user