git log –since=”1 year ago”: One Year of apt-upgrade.me && Publishing the Ansible Playbooks for Nextcloud on K3s

Illustration: a man celebrating a first anniversary with fireworks and technology logos

🌐 Also available in: Deutsch · Français · Español

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.

Table of Contents

🚀 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

MIT licence, no warranty – please review it yourself before using it.

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. 😄

Some text and images on this site are AI-assisted or AI-generated. Details