Capstone — Building a Real Multi-Unit Service Stack
systemd in Depth
Chapter 11 · Capstone — Building a Real Multi-Unit Service Stack
The final chapter. A real small app — notesapp — deployed as a properly dependent, resource-limited service with a companion maintenance timer, logged and verified using every tool this course has built. This is where ws1's and ansible1-10's own unexplained systemctl/service calls finally get their full answer.
The Target — A Real Small App: notesapp
A small notes-taking web service: a long-lived process, needs the network genuinely up before starting, running on a modest, shared VPS where resource limits matter, and needing a periodic cleanup job to purge notes older than 90 days.
Step 1 — The Service Unit
Note the paired Wants= and After= for network-online.target — systemd1-4's own central lesson, applied for real: After= alone would never guarantee networking is actually started; the pair together genuinely guarantees both that it starts and that it starts first.
Step 2 — Resource Limits
These values were chosen only after observing notesapp's own real memory and CPU usage via systemd-cgtop over a representative period — systemd1-9's own warn-box, applied honestly here rather than picking arbitrary numbers.
Step 3 — Applying and Verifying
daemon-reload is not optional (systemd1-3's own gotcha); the final command confirms the resource limit actually took effect, exactly systemd1-9's own verification step.
Step 4 — The Companion Maintenance Timer
systemd1-7's own two-unit pattern, applied for real — the cleanup logic is a genuine, independently-testable Type=oneshot service; the timer only decides when it runs.
Step 5 — Reading the Logs
The first watches notesapp live after its first start; the second confirms the daily cleanup timer's own most recent run actually executed and logged correctly — systemd1-6's own tools, put to real use.
Step 6 — Verifying Boot Impact
An honest use of systemd1-10's own tool — confirming notesapp's real position (or lack of one) in the boot's actual critical path, rather than assuming a new service must automatically be a boot-time concern just because it's new.
Closing the Loop on ws1 and ansible1-10
Every systemctl command run throughout this capstone — status, restart, enable, daemon-reload — is now fully understood. This resolves exactly what ws1's own hardening chapters and ansible1-10's own capstone left unexplained at the very start of this course.
| Piece | Chapter |
|---|---|
| Unit file structure, systemctl basics | systemd1-2 |
| Type=simple, Restart=, daemon-reload | systemd1-3 |
| Paired Wants=/After= | systemd1-4 |
| WantedBy=multi-user.target / timers.target | systemd1-5 |
| journalctl verification | systemd1-6 |
| The .timer + .service pattern | systemd1-7 |
| CPUQuota=/MemoryMax=/MemoryHigh=, verified via systemctl show | systemd1-9 |
| systemd-analyze critical-chain verification | systemd1-10 |
systemd1-8's own material is a real, honest non-fit here: an always-on, frequently-hit web-facing app doesn't benefit from on-demand startup the way that chapter's own rarely-used-service example did. No systemd-managed sandboxing directives (ProtectSystem=, NoNewPrivileges=, and similar real, common production-hardening options) were covered anywhere in this course and aren't used here either. Logging stays local to journald — no centralized Loki-style shipping is actually configured, only discussed as a real possibility back in systemd1-6. And this capstone is single-host only, with no clustering or multi-host coordination — genuine next steps, not oversights.
Hands-On Exercises
Explain why notesapp.service uses both Wants= and After= for network-online.target rather than just After= alone, using this chapter's own reasoning.
📄 View solutionExplain why the notesapp-cleanup task is written as a separate Type=oneshot service paired with a timer, rather than being built directly into notesapp.service's own ExecStart= somehow.
📄 View solutionA colleague suggests socket-activating notesapp.service to save resources. Give an honest answer, using this chapter's own scope note, on whether that's a good idea for this specific app.
📄 View solutionChapter 11 Quick Reference — systemd in Depth Complete
- notesapp.service combines paired Wants=/After=, Restart=on-failure, and a dedicated User= — every piece from systemd1-2 through systemd1-4
- CPUQuota=/MemoryMax=/MemoryHigh= chosen only after observing real usage via systemd-cgtop, per systemd1-9's own discipline
- notesapp-cleanup.timer/.service — a real .timer + .service pairing, daily, with Persistent=true catch-up
- journalctl and systemd-analyze critical-chain both verify the deployment honestly, not just assumed to be correct
- Every systemctl command from ws1's and ansible1-10's own unexplained calls is now fully understood
- Honest scope note: no socket activation (a real non-fit here), no sandboxing directives, journald only (no centralized shipping configured), single-host
- The full systemd in Depth course — 11 chapters — is now complete.