There comes a point in every homelab journey where you look at your pile of
bash scripts, hand-crafted config files, and sticky notes that say things like
“remember to chown /data before starting” β€” and you decide:
enough. πŸ˜…

This is that post. Today I’m publishing the Ansible playbooks I’ve been using to deploy
and operate Nextcloud ☁️ and WordPress πŸ“ on a
single-node K3s cluster running on AlmaLinux 9.
Everything is infrastructure-as-code, fully idempotent,
and comes with a complete operations guide.

But first, a brief word from our sponsors β€” by which I mean:
a moment of involuntary self-reflection. πŸ™‚

πŸŽ‰ Sidebar: Wait, this blog has a birthday?

Yesterday marked exactly one year since the very first post went live on this blog.
It contained exactly two words: Hallo Welt.
Which is German for Hello World,
which is programmer for:

“I have absolutely no idea what I’m doing yet, but I have a domain, a VPS,
and by git push I will use both.”
πŸš€

Frankly, I did not expect this blog to still exist a year later.
Most of my side projects follow a very predictable lifecycle:

git init β†’ burst of enthusiasm β†’ three commits β†’
git stash β†’ forgotten forever πŸ’€

Yet here we are at one year uptime, which in personal-project terms
is roughly equivalent to a Fortune 500 company celebrating its centennial.

The metrics are accordingly impressive:

  • πŸ“š several posts
  • πŸ‘€ at least two readers (one of whom is me checking mobile layout)
  • πŸ“ˆ a Grafana dashboard nobody except me has ever looked at

Achievement unlocked. πŸ†

We now return to the actual topic of this post.

πŸš€ What This Repository Does

The codebase I’m publishing today is an Ansible-based infrastructure toolkit
that provisions and manages workloads on a
single-node K3s cluster running on AlmaLinux 9:

  • ☁️ Nextcloud β€” self-hosted file sync and collaboration,
    including Collabora CODE for online office editing
  • πŸ“ WordPress β€” for exactly the kind of blog you’re reading right now

Both stacks share a common set of Ansible roles covering:
SSH hardening πŸ”,
firewall rules πŸ›‘οΈ,
monitoring πŸ“Š,
mail relay,
automatic security updates,
rootkit scanning,
and audit logging.

The playbooks are designed to be idempotent:
you can run them repeatedly against a live system and they only change what actually
needs changing. ✨

The entire infrastructure is described in code.
There are no hidden manual steps buried in a notebook somewhere.
If the server burns down (figuratively β€” it’s a VPS πŸ˜„),
a new one can be provisioned from scratch with a single
ansible-playbook command and a restore from backup.

☸️ Architecture: Nextcloud on K3s

The core design decision is a single-node Kubernetes cluster using K3s.
No high availability. No multi-master setup. No etcd cluster.
Just one server, one node, one point of failure β€” intentionally.

For this kind of workload, the operational simplicity is worth far more than pretending
to run a hyperscaler in a homelab. πŸ™‚

K3s still gives us all the good Kubernetes features:

  • βš™οΈ declarative manifests
  • πŸ”„ rolling deployments
  • πŸ” cert-manager
  • 🌍 ingress controllers

…without the operational overhead.

πŸ›‘οΈ Security: Defence in Depth

The security model is layered. No single mechanism is trusted exclusively.
Instead, the stack combines multiple independent controls:

  • πŸ”₯ iptables with default DROP policies
  • πŸ” hardened SSH configuration
  • 🚫 Fail2Ban for brute-force protection
  • ⚑ nginx rate limiting
  • 🧩 SELinux in enforcing mode
  • πŸ”‘ encrypted secrets via Ansible Vault
  • πŸ•΅οΈ auditd + rkhunter monitoring
  • πŸ“¦ automatic security updates

Or, put differently:
defence in depth, not
“I hope nobody scans this VPS.” πŸ˜„

πŸ“Š Monitoring: Prometheus + Grafana

Monitoring is fully automated using:
Prometheus πŸ“ˆ,
Grafana πŸ“Š,
Node Exporter,
mysqld_exporter,
and PHP-FPM metrics.

Dashboards are automatically imported and configured by the playbook,
including:

  • πŸ“ˆ Node Exporter Full
  • πŸ—„οΈ MySQL Overview
  • ⚑ PHP-FPM metrics

Because if you’re going to over-engineer a homelab,
you might as well have beautiful graphs showing exactly how over-engineered it is. πŸ™‚

πŸ’Ύ Backup and Restore

The repository includes actual backup and restore scripts β€”
not just optimistic feelings and a folder named
backup_final_v2_REAL. πŸ˜…

Backups cover:

  • πŸ—„οΈ MariaDB dumps
  • πŸ“‚ Nextcloud application files
  • ☁️ user data
  • πŸ”„ restore automation

After a restore, re-running the playbook ensures all configuration and
application settings are brought back into the expected state automatically.

πŸš€ Getting Started

The repository targets a fresh AlmaLinux 9 installation.
The setup process is intentionally straightforward:

  1. βš™οΈ Configure your inventory and host variables
  2. πŸ” Encrypt your vault with Ansible Vault
  3. πŸ“¦ Install the required Ansible collections
  4. πŸš€ Run the playbook

The deployment is fully idempotent, meaning future changes β€”
whether image updates, firewall tweaks, or additional apps β€”
can simply be rolled out by running the playbook again.

πŸ“₯ Download

The full repository is available as a ZIP archive.
Extract it, fill in your host_vars,
encrypt your vault, and you’re ready to deploy:

unzip www_k3s.zip
cd www_k3s
ansible-galaxy collection install -r requirements.yml
ansible-vault encrypt inventory/host_vars/<host>/vault.yml
ansible-playbook nextcloud-k3s.yml --limit <host> --ask-vault-pass

πŸ‘‰

Download www_k3s.zip

Questions, issues, or suggestions? πŸ’¬
Drop them in the comments below.

Small note: I still haven’t fully sorted out how I want to publish and maintain
the code properly via GitLab yet, so for now the current state is provided as a simple
ZIP archive πŸ“¦. Long-term I may move everything to GitHub or another public repository,
but that’s still an open infrastructure debate between me, my free time, and several
unfinished TODO lists. πŸ˜„

By raphael

Leave a Reply