Four packages, one playbook. This should be a boring afternoon, and it mostly is — except for one ordering problem that breaks almost every tutorial on the subject, in a way that only shows up on a genuinely fresh server.
So let me start there, because if you understand it the rest is just YAML.
The deadlock nobody warns you about
You write an nginx vhost template with TLS configured, because that is the end state you want:
If you came here looking for how to install open-source Puppet on a current Ubuntu, the short answer is that you can't, and it is not your fault for not knowing. The instructions everyone wrote — add deb.puppetlabs.com, install puppet-agent — stopped producing new versions in early 2025.
This article covers what to do instead. If you are running Puppet today, the migration is at the bottom and it is less painful than you are probably expecting.
Run Emacs inside tmux with the stock configuration and you hit the problem within about four seconds. tmux's prefix key is C-b. In Emacs, C-b is backward-char — one of the six or seven keys you press most in any editing session.
So every tmux command now begins by moving your cursor one character left, and every attempt to move left now opens a tmux prefix that swallows whatever you type next. Both tools are behaving correctly. They just want the same key, and the collision is on a binding neither side can reasonably give up.
Every other piece of a search engine operates on data you already have. The crawler is the one component that reaches out and spends somebody else's money — their bandwidth, their CPU, their database connections — without asking first.
So far in this series we have crawled pages, broken them into tokens, and built an index that can tell us which documents contain a term. That leaves the question every search engine actually lives or dies by: in what order do we show them?
A query for "puppet ubuntu" might match four hundred documents. The user will look at five. Getting those five right is the ranking problem, and for keyword search the answer has been more or less settled since the 1990s: BM25.
Most guides to enabling REST or JSON:API in Drupal tell you to go to /admin/config/services, tick some checkboxes, and save. That's fine advice for a one-off experiment, and terrible advice for anything you intend to depend on. A permission granted by clicking a checkbox in an admin UI leaves no diff, no code review, no record of who enabled it or why, and no way to reproduce it on staging without either remembering to click the same checkbox there too or exporting config after the fact and hoping you caught everything it touched.
Every year, on schedule, someone writes the obituary for self-hosted email. The argument is always the same shape: Gmail and Microsoft control the inbox, residential and small-VPS IP ranges are pre-blocked before you've sent a single message, and the only rational move is to hand your mail to a SaaS provider and get on with your life. I've heard some version of this argument every year for the last five, and every year I've kept running my own Postfix and Dovecot stack on a cheap VPS, for one domain, for myself. It still works. Mail still arrives. Nothing is on fire.
Most developers begin their careers thinking almost exclusively about applications.
They think about features, programming languages, frameworks, databases, and APIs. Success is measured by whether the application works, whether users can accomplish their tasks, and whether the next feature can be delivered before the deadline.
As software matures, however, another reality begins to emerge.