How is this blog generated?

3.6.2025

As I outlined in the first entry, this entire site is generated using ansible templates and a simple playbook. For now, it fits the purpose surprisingly well. Sure, I can't neatly write just a bunch of .md-files and sprinkle some metadata on top, but I don't think I'd need to. Being honest, I'll most likely update this blog every few months at most, so small burden from having to repeat myself and essentially write these pages in pure HTML is not that significant.

In this blog post, I'll go through the setup and list down the main considerations behind it. I'll explain the deployment model and automation in a later post.

List of design principles

How are these principles implemented?

Simply put, all content is stored and versioned as templates in a directory structure like this:

$ tree templates
templates
|-- blog
|   |-- blog-generation.j2
|   |-- first-entry.j2
|-- blog.html.j2
|-- footer.j2
|-- header.j2
|-- index.html.j2
|-- list.html.j2
|-- onnilampi.fi.container.j2
        

The template directory also contains a template for managing the site container, but as said, more on that in a later post.

Templates are utilized by a simple Ansible playbook, which first generates a directory in the site hierarchy, and then places an index-file in that directory. Here is an example of how the blog posts are handled:

        
- name: Generate directories for blog entries
  ansible.builtin.file:
    path: "./site/blog/{{ item }}"
    state: directory
    mode: '0755'
  with_items:
    - first-entry
    - blog-generation

- name: Generate blog entry
  ansible.builtin.template:
    src: "blog/{{ item }}.j2"
    dest: "./site/blog/{{ item }}/index.html"
    mode: '0644'
    validate: "tidy -eq %s"
  with_items:
    - first-entry
    - blog-generation
            

The tasks above simply loop through all the declared templates, output .html-files, and use tidy to validate the output. This ensures that all output is valid HTML, and weird templating or formatting errors are caught early on.

The implementation has a tiny trap in the sense that all new blog posts need to be updated in three places: in two Ansible tasks and one static HTML-file. There really is no way around this as long as I want to keep the setup light, but luckily the potential damage of this is relatively easy to mitigate. In total, there are 8 different combinations of what can happen:

Directory task Template task Blog index Outcome
UPDATED UPDATED UPDATED All works as expected
UPDATED UPDATED NOT UPDATED Hidden entry is generated
UPDATED NOT UPDATED UPDATED Empty directory created
UPDATED NOT UPDATED NOT UPDATED Empty directory created
NOT UPDATED UPDATED UPDATED Templating will fail
NOT UPDATED UPDATED NOT UPDATED Templating will fail
NOT UPDATED NOT UPDATED UPDATED Broken link is added to index
NOT UPDATED NOT UPDATED NOT UPDATED Nothing happens

As seen in the table, several different situations can happen. 3/8 are non-intrusive, so those don't require any action. The two failures are desired, and the other three should preferably also fail. As I'm a lazy person, I simply implemented a task that fails if there are empty directories:

 
- name: Check that there are no empty directories
  ansible.builtin.command:
    argv:
      - find
      - ./site
      - -empty
      - -type
      - d
  register: empty_dirs
  changed_when: false
  failed_when: "empty_dirs.stdout_lines | length != 0"
        

Down the line, I might write an ansible module to check for the broken links. For now, I'll just accept the fact that it is possible to add broken links into the blog index. The two remaining outcomes of creating hidden entries and doing nothing are considered features for now.

Conclusion

I'm relatively happy with the result, to be honest. Most of the requirements are fulfilled quite well, and this current implementation contains a minimal amount of code. There are also several interesting opportunities for future development work when it comes to generating a static site with Ansible. Things such as automatically checking for broken links and generating an RSS feed are on the roadmap.

Stay tuned for part 2, where I'll cover all the deployment tooling and infrastructure!

Go back to list of entries