DropWatch is an outbound-only worker. It needs DNS, HTTPS, and optional SMTP egress, but it must never receive a public domain, reverse-proxy route, or published container port. GitHub publishes immutable release artifacts; Dokploy pulls one approved image digest and deploys it manually.
Start from the repository root on a clean main branch:
git switch main
git pull --ff-only
make verify
make smoke
make smoke-live
make compose-check
make security
git status --short --branchmake smoke-live is the only optional command because it depends on public
IANA, RDAP, and DNS services. All other commands must pass. The final Git status
must show main aligned with origin/main and no uncommitted files.
Before each stable tag, confirm that:
- CHANGELOG.md contains the intended version and date;
- all Dependabot pull requests have been reviewed, merged, or deliberately deferred;
- the commit intended for release is already on
main; - the
CIandSecurityworkflows are green for that exact commit.
The canonical public repository is
Simplarix/dropwatch.
Open Repository → Settings → General and configure:
- Keep the default branch set to
main. - Keep the repository public and retain the MIT license.
- Under Pull Requests, enable squash merge and rebase merge, but disable
merge commits if the
mainruleset requires linear history. Use rebase merge when a pull request already contains deliberately separated commits; use squash merge for noisy work-in-progress histories. - Enable automatic deletion of head branches after a pull request is merged.
- Keep Issues enabled if the included issue templates will be used.
Before publishing previously private material, review the complete Git history, release assets, issues, and pull requests for credentials and private data. Keep internal deployment notes and communication drafts outside the repository.
Open Repository → Settings → Actions → General:
- Allow GitHub-owned actions. The current workflows use only
actions/checkoutandactions/setup-go, both pinned to full commit SHAs. - Under Workflow permissions, select Read repository contents and packages permissions.
- Leave Allow GitHub Actions to create and approve pull requests disabled.
The release workflow declares its own narrowly scoped contents: write and
packages: write permissions. CI and Security retain read-only permissions.
Open Repository → Settings → Rules → Rulesets → New ruleset → New branch ruleset:
- Name it
main-protection. - Set Enforcement status to Active.
- Target the default branch, or include the exact pattern
main. - Keep the bypass list empty unless an emergency administrator bypass is operationally required.
- Enable:
- restrict deletions;
- block force pushes;
- require linear history;
- require a pull request before merging;
- require conversation resolution before merging;
- require status checks to pass;
- require branches to be up to date before merging.
- Add these three GitHub Actions checks from a recent successful run:
verify;Source and dependency scan;Container and configuration scan.
For a team repository, require at least one approval, dismiss stale approvals after new commits, and require a CODEOWNER review. A sole maintainer cannot approve their own pull request; in that case keep the pull-request and status check requirements but do not create an approval rule that permanently blocks the maintainer.
Do not enable Restrict updates for main unless the bypass list has been
designed carefully; the pull-request and required-check rules already prevent
unreviewed direct changes.
Open Repository → Settings → Rules → Rulesets → New ruleset → New tag ruleset:
- Name it
stable-release-tags. - Set Enforcement status to Active.
- Include tags matching
v*. - Add only the release maintainer or release team to the bypass list. Without this bypass, nobody can create the first stable tag.
- Enable:
- restrict creations;
- restrict updates;
- restrict deletions;
- block force pushes.
The workflow independently rejects every tag that is not exactly
vMAJOR.MINOR.PATCH and every release commit that is not reachable from
origin/main.
Open Repository → Settings → Environments → New environment and create an
environment named exactly production, matching the release workflow:
- Under deployment branches and tags, select Selected branches and tags.
- Add the tag pattern
v*; do not add normal branches. - If required reviewers are available, add a release reviewer and prevent self-review when at least two maintainers are available.
- Do not add Dokploy credentials or notification secrets. This environment gates publication only; the workflow never connects to Dokploy.
If the control is unavailable, the protected-tag bypass list and deliberate creation of a signed stable tag are the manual release gate.
Open Repository → Settings → Security & analysis and enable every feature available for the current plan:
- dependency graph;
- Dependabot alerts;
- Dependabot security updates;
- secret scanning;
- push protection;
- private vulnerability reporting.
If Dependency Review is available, require it for pull requests as an
additional main ruleset check. Keep the weekly Security workflow enabled.
Choose the next unused stable version from CHANGELOG.md.
Replace vX.Y.Z below with that version; never reuse an existing tag. Create a
signed tag only after the protected main commit is green:
git switch main
git pull --ff-only
git status --short --branch
git tag -s vX.Y.Z -m "DropWatch vX.Y.Z"
git tag -v vX.Y.Z
git push origin vX.Y.ZIf signed tags are not yet configured, set up a verified GPG or SSH signing key before releasing. The workflow also accepts an annotated tag, but a verifiable signed release tag is preferred.
The Stable release workflow:
- verifies the exact stable tag and its ancestry from
main; - reruns verification, Compose validation, and the security gate;
- builds Linux
amd64andarm64binaries and checksums; - publishes
ghcr.io/simplarix/dropwatch:vX.Y.Z; - attaches OCI provenance and an SBOM;
- creates
image-digest.txtwith the immutable multi-architecture digest; - creates the GitHub Release and attaches all release files.
Approve the production job if the environment has a reviewer gate. After the
workflow finishes, verify that the GitHub Release contains:
dropwatch-linux-amd64;dropwatch-linux-arm64;checksums.txt;image-digest.txt;LICENSE(from v0.1.10 onward).
The digest file must contain one complete line like:
ghcr.io/simplarix/dropwatch@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
Never deploy the tag, latest, v1, or v1.2. Deploy only this digest line.
Never delete, move, or rebuild a published version; fixes use a new patch
release.
The first successful release creates the organization package. Open the
dropwatch package under the Simplarix organization and verify:
- the package is linked to
Simplarix/dropwatch; - the package visibility is public (separate from repository visibility);
- repository Actions access permits
Simplarix/dropwatchto publish; - the new version and its digest are visible;
- the exact release digest can be pulled without registry credentials.
Set package visibility in Organization → Packages → dropwatch → Package settings → Change visibility → Public. Verify an anonymous pull from a clean Docker credential configuration before announcing container availability.
The workflow uses its short-lived GITHUB_TOKEN; no long-lived publishing token
belongs in GitHub secrets.
Keep the Dokploy UI, API, SSH, Docker socket, and host administration private. Prefer WireGuard or Tailscale. If public SSH is unavoidable, restrict it by source address, require keys, disable passwords, and disable direct root login.
Use both the VPS provider firewall and host firewall:
- no inbound rule is required for DropWatch;
- outbound UDP/TCP 53 is required for DNS;
- outbound TCP 443 is required for IANA, RDAP, Discord, GitHub, and GHCR;
- the configured SMTP port, normally TCP 587, is required only when SMTP is enabled.
Keep the host OS, kernel, Docker Engine, and Dokploy on supported versions with
security updates enabled. Remember that Docker manages firewall rules; validate
the effective DOCKER-USER or nftables policy after firewall changes.
The repository and GHCR image are public. In Dokploy, use the GitHub provider or
public HTTPS Git URL https://github.com/Simplarix/dropwatch.git to read
compose.production.yml. The public image can be pulled without a registry token.
An existing read-only GitHub/registry connection may remain in use. Keep its credentials private; no write access is needed to read a Compose definition or pull an image. Public artifacts do not require public access to Dokploy, SSH, Docker, the database, or notification credentials.
In Dokploy:
- Create project
dropwatch. - Create environment
production. - Add a Compose service named
dropwatch. - Select Docker Compose, not Docker Stack.
- Select the GitHub or Git provider configured above.
- Select repository
Simplarix/dropwatch, branchmain, and Compose pathcompose.production.yml. - Disable automatic deployment from normal
mainpushes. Production rollout is intentionally manual after a successful stable release. - Do not configure a domain, Traefik route, proxy, published port, or additional public network.
The Compose definition already enforces:
- non-root user
10001:10001; - read-only root filesystem;
- all Linux capabilities dropped;
no-new-privileges;- bounded CPU, memory, processes, temporary storage, and logs;
- a named persistent
/datavolume; - an ephemeral
/tmp; - no inbound port;
- a binary healthcheck.
Open the Compose service Environment tab. Dokploy writes these values to its
deployment .env; compose.production.yml explicitly passes the supported
variables into the container.
Set:
DROPWATCH_IMAGE=ghcr.io/simplarix/dropwatch@sha256:REPLACE_WITH_RELEASE_DIGEST
DROPWATCH_DATABASE_PATH=/data/dropwatch.db
DROPWATCH_LOG_LEVEL=info
DROPWATCH_LOG_FORMAT=json
DROPWATCH_TIMEZONE=Europe/Berlin
DROPWATCH_MAX_CONCURRENCY=4
DROPWATCH_HTTP_TIMEOUT=15sReplace DROPWATCH_IMAGE with the exact complete line from the GitHub Release.
UTC is also a valid timezone if local display is not required; storage always
remains UTC.
Configure at least one notification channel.
For Discord:
DROPWATCH_DISCORD_WEBHOOK_URL=For SMTP:
DROPWATCH_SMTP_HOST=smtp.example.com
DROPWATCH_SMTP_PORT=587
DROPWATCH_SMTP_USERNAME=
DROPWATCH_SMTP_PASSWORD=
DROPWATCH_SMTP_FROM=dropwatch@example.com
DROPWATCH_SMTP_TO=operator@example.com
DROPWATCH_SMTP_REQUIRE_TLS=trueFill the enabled channel’s empty credential values privately in Dokploy.
DROPWATCH_SMTP_TO accepts a comma-separated recipient list. Keep TLS required.
Omit the unused channel's variables instead of inventing placeholder
credentials. Treat Dokploy administrators as secret-bearing administrators
because Docker administrators can inspect container environments.
Before deployment, use Dokploy's rendered Compose preview or run
docker compose config and confirm that:
- the image is the approved
ghcr.io/simplarix/dropwatch@sha256:...digest; - the selected timezone and concurrency values are present;
- the enabled notification channel appears in the container environment;
- no ports, domains, or proxy labels appear.
Start the deployment manually. Wait for the container healthcheck to become healthy. On the VPS, first identify the exact DropWatch container, then verify:
docker ps --filter name=dropwatch
docker inspect --format '{{.Config.Image}}' CONTAINER
docker inspect --format '{{.Config.User}} {{.HostConfig.ReadonlyRootfs}} {{json .HostConfig.CapDrop}} {{json .NetworkSettings.Ports}}' CONTAINER
docker exec CONTAINER dropwatch health --json
ss -lntupExpected results:
- image is the approved immutable digest;
- user is
10001:10001; - read-only root filesystem is
true; - capability drop contains
ALL; - published ports are empty;
- health reports database, migrations, write access, watcher, and heartbeat as healthy;
- DropWatch does not appear as a listening host service.
Test notifications:
docker exec CONTAINER dropwatch notify test
docker logs CONTAINERThe command must report the configured channel as sent. Logs must not reveal a password, complete webhook URL, or token.
Add the initial monitored domains only after health and notification checks:
docker exec CONTAINER dropwatch domain add example.com
docker exec CONTAINER dropwatch domain list
docker exec CONTAINER dropwatch history example.com --limit 20Replace example.com with the intended domains. The default add command performs
an initial live check.
The Compose named volume contains:
/data/dropwatch.db;- SQLite WAL and shared-memory files while the process is running;
- the RDAP bootstrap cache.
Configure a Dokploy Volume Backup for the actual project-prefixed named volume and an encrypted S3-compatible destination. A raw volume copy while SQLite is writing is not guaranteed to be application-consistent. Use a SQLite-aware backup or stop DropWatch cleanly before the volume snapshot. Never copy only the database file while ignoring its WAL state.
Before calling the deployment production-ready:
- create one backup;
- restore it into an isolated test volume;
- start the same image against the restored volume;
- verify
dropwatch health, the domain list, and history.
Rollback by setting DROPWATCH_IMAGE to a previously approved digest and
redeploying. Only roll back when that binary is compatible with all migrations
already applied to the persistent database. Never retag or overwrite an
existing release.
- GitHub
mainruleset is active. - Stable tag ruleset is active and has a usable release-maintainer bypass.
productionenvironment targets stable tags.- Repository security features are enabled.
- Stable release workflow is green.
- GHCR package is public, linked, and anonymously pullable by release digest.
- Dokploy and host administration are private.
- Compose uses the release digest and has no inbound route.
- At least one notification channel passes its live test.
- Initial domains are present and have successful history.
- Healthcheck is healthy.
- Backup and isolated restore are proven.