Traefik v3
OperationalReverse proxy · TLS · routing
Terminates TLS with ACME certificates, redirects HTTP to HTTPS, and routes to containers by Docker label. The only container publishing ports on the host.
- Docker provider
- ACME
- no-new-privileges
Single-node Ubuntu Server 24.04 LTS · Hostinger VPS
A containerized edge/application stack behind Traefik, a host-native AI agent under systemd, layered host hardening, and monitoring proven by a deliberate failure test. Most of the engineering effort went into auditing and verification, not installation — and the parts that are unfinished say so.
No application port published on the host.
TLS termination, ACME, HTTP → HTTPS redirect.
Blast radius bounded by Unix permissions.
AI-assisted engineering with human validation.
Scope note. Encrypted off-site backup is implemented, automated and validated by a real data restore. A full-VPS rebuild drill and a measured recovery time are still pending. Nothing on this page is claimed before it is true — see current status.
01 — System
Every request enters through the same door. Application containers publish nothing on the host; they are reachable exclusively through the reverse proxy over an internal Docker network.
Perimeter
allow 22 · 80 · 443 only
Ubuntu Server VPS
Docker
network: proxy — internal, IPv4
no host port publication
systemd — host-native
Adding a service means adding Traefik labels, not opening a host port. The Traefik dashboard is not publicly exposed and insecure is off.
Traefik only needs to read container metadata. Read-only mounting limits — but does not eliminate — the blast radius; socket access is still privileged and treated as such.
Not inside it. It declares no listener, holds no admin SSH keys, and is not in the docker group.
02 — Workloads
Four components, each with a stated job and a stated boundary.
Reverse proxy · TLS · routing
Terminates TLS with ACME certificates, redirects HTTP to HTTPS, and routes to containers by Docker label. The only container publishing ports on the host.
Static landing page
The public entry point, and the workload used for the deliberate failure test that validated the alerting chain end to end.
Automation orchestration
Holds real credentials, so it is the stateful component that drives the backup design. Monitored on its /healthz/readiness endpoint — usability, not "the process exists".
Health monitoring & alerting
HTTP checks against the public endpoints plus a push monitor for the host-native agent. Alerts go to a chat channel via a bot deliberately separate from the agent's.
Host-native AI agent under systemd, with constrained Unix privileges
Runs as its own dedicated user managed by systemd — not as root and not inside Docker. It has no sudo rights, no docker group membership, no access to the Docker socket, no copy of the administrator's SSH keys, and no publicly exposed port. Its liveness is proven by an external heartbeat rather than self-reporting: a systemd timer checks every 60 seconds whether the gateway unit is actually active, and pushes only when it is.
03 — Hardening
Layered, and each layer validated independently rather than assumed. The controls below were confirmed active against the running system, not merely written into files.
04 — Reliability
Two complementary signals, and one blind spot that is documented rather than hidden.
Signal 1
Uptime Kuma polls the public endpoints through the same path a user takes. Alerts route to a chat channel using a bot deliberately separate from the AI agent's bot, so a failure of the agent cannot silence alerting.
n8n is checked on /healthz/readiness rather than /healthz: all three health paths return 200, and readiness is the one that reflects usability.
Signal 2
The host-native agent exposes no HTTP endpoint, so it cannot be polled. A systemd timer fires every 60 seconds, a oneshot unit checks whether the gateway service is genuinely active, and pushes to Uptime Kuma only when it is. A missing push raises an incident.
Verified live: timer enabled and waiting, last invocation exiting 0/SUCCESS, next firing scheduled, script present at restrictive ownership and mode — contents deliberately left unread, because it is secret-bearing.
The whole chain is validated against a real workload rather than against a config file. Traefik's /ping was deliberately not enabled: application endpoints already give a stronger signal, and enabling it would add surface for no gain.
05 — Recoverability
Encrypted off-site backup is implemented, automated and verified by a real restore. The honesty of this section is still the point: the data-restore path is proven, a full-VPS rebuild has not been drilled, and no recovery time is claimed until it is.
06 — Method
This project was built with heavy AI assistance and deliberate human control. Both halves are true, and stating only one of them would be dishonest.
Defines the goal and the constraints.
Investigates, drafts commands, reviews configuration, identifies risk.
Is it necessary? Is it reversible? What breaks?
Rejected or revised → back to step 02
Privileged steps run manually via interactive sudo.
Checked against real system state.
Contradicts expectation → back to step 02
Evidence, decisions, and open TODOs.
Model output is an engineering aid or a hypothesis. It is never ground truth.
The design rule: an assistant's blast radius should be bounded by Unix permissions, not by its own good behaviour. Model behaviour is a probability distribution; file permissions are not.
07 — Evidence
Each of these started as a plausible conclusion and ended somewhere else, because the system was consulted instead of assumed.
Configuration is a hypothesis
The base sshd_config shipped with a late PermitRootLogin yes, while an Included drop-in set PermitRootLogin no. OpenSSH takes the first obtained value, so the drop-in wins — but that is a claim about parsing order, not evidence. It was settled by reading the effective configuration.
sudo sshd -t # syntax check before any reload
sudo sshd -T | grep '^permitrootlogin '
# → permitrootlogin no
Outcome: on-disk configuration is a hypothesis; sshd -T is the evidence. Had the reasoning been wrong, the difference between "we discussed it" and "we checked it" would have been remote root login.
Anomaly, not vulnerability
IP forwarding was enabled at runtime with no assignment anywhere in /etc/sysctl.conf, /etc/sysctl.d/, /usr/lib/sysctl.d/ or /lib/sysctl.d/. Correlating with the live Docker iptables chains and a DROP IPv4 FORWARD policy showed it to be runtime state owned by Docker bridge networking.
Outcome: recorded as an inference, not a fact — no persistence file proves ownership. The operational conclusion followed from the uncertainty: leave it alone, and do not add a duplicate persistent sysctl entry asserting an ownership nobody established.
Reconciliation, not correction
An unprivileged audit fetched the monitoring root without following redirects and got 302 → /dashboard, appearing to contradict an earlier "final 200" record. Both were correct — they measured different points in one flow (GET / → 302 → /dashboard → 200).
Outcome: the real finding was a hidden dependency — the monitor is only correct while it follows redirects. Accepting 200–299 without following would turn a healthy service into a permanent false alarm.
Storage engines have opinions
Both application databases run SQLite in WAL mode, and at inspection each -wal file was larger than its main database — nearly three times larger for the automation platform. A naïve copy of the .db during writes would have omitted most of the live state.
Outcome: the pipeline was designed around an application-consistent snapshot before any backup code was written — and the audit also surfaced the encryption-key file that a restore would silently need.
08 — Roadmap
Where something is unfinished, it says so. Where a conclusion is an inference rather than a measurement, it says that too.
| Area | Status |
|---|---|
| Edge routing, TLS, HTTP → HTTPS redirect | Operational |
| Application stacks behind the proxy | Operational |
| Host hardening baseline | Operational, audited |
| Agent under systemd with bounded privileges | Operational |
| Monitoring + alerting, validated by a real failure test | Operational |
| Path-level backup inventory | Implemented |
| SQLite consistency mechanism, six databases | Validated |
| Restic + Backblaze B2, encrypted off-site | Operational |
| Automated daily backup, retention and weekly maintenance | Operational |
| Restore test, data | Validated |
| Full-VPS rebuild drill and measured RTO | Not performed |
| DR runbook | Partial |
| External backup-failure alerting | Planned |
| Independent external monitoring | Planned |
| Image tags resolved to immutable digests | Open |
Backup is no longer the open item: it runs on a schedule, it is verified, and a real restore has been performed. What is still open — a timed full-VPS rebuild drill and off-host failure alerting — is written here as open rather than folded quietly into the word recoverable.
09 — Source
The public repository contains documentation and sanitized examples only. No hostnames, IP addresses, credentials, tokens, keys, database files or production paths appear in it — every example configuration is an educational reconstruction using placeholder values.
AI Automation Engineer · Automation Engineer · Platform / Infrastructure Engineer · DevOps-adjacent · Solutions Engineer — containerized service delivery behind a reverse proxy, host hardening and privilege design, monitoring validated by real failure, backup reasoning grounded in storage-engine behaviour, and a documented method for working with AI assistants in privileged environments.