My Blog Pure Blog Deployment

meysamazad1 pts0 comments

My Blog Pure Blog Deployment - This is my blog now

My Blog Pure Blog Deployment

15 May 2026

As you might know, I recently migrated my blog from Hugo to Pure Blog. This was a nontrivial switch because Hugo is a static site generator (meaning it generates a bunch of HTML, CSS and JS that you just have to throw at a webserver and be done), whereas Pureblog is a PHP application with state, a login page and so on. So I'd like to take the time to write down how I deploy it and why. I realize that this is yet another "this is my blogging setup" post, but I also think that it just might be instructive to one or two other people out there.

My requirements are as follows:

Have a local dev environment to tinker and test

Keep everything in git (backup, rollbacks, traceability)

Embed Pure Comments and serve from /comments

From these a couple of questions arise:

How to sync local environment and server?

How to keep secrets out of git (Pure Comments has a sqlite instance and a sensitive config file)

How to actually serve Pure Blog?

I started by addressing the last point on that list. I already have caddy, a webserver, running on my server which previously served my static website, alongside a few other things. Pure Blog being a PHP website, I could have done this as well but the application is really actually meant to be served by Apache. It includes .htaccess files and all that and I really want to avoid any overhead from translating the project onto my infrastructure.

So I containerized it. Incidentally, this also addresses my need for a local environment because it's very straightforward to spin up a container locally, make changes until I'm satisfied and push the changes to remote. More on that later.

I run everything in Docker containers. I prefer isolating the stuff from my OS, it helps with reproducibility, I can keep the necessary infrastructure as code, and I am familiar with the concepts from work. If you want to cite a saying with hammers and nails at this point, go ahead.

I spent an inordinate amount of time crafting a Dockerfile for everything and here is the result:

FROM debian:trixie-slim

# Prevent interactive prompts during package installation<br>ENV DEBIAN_FRONTEND=noninteractive

# Install Apache, PHP, and necessary modules<br># Note: libapache2-mod-php installs the Apache module automatically<br>RUN apt-get update && apt-get upgrade -y && \<br>apt-get install -y \<br>apache2 \<br>libapache2-mod-php && \<br>php \<br>php-mbstring \<br># For using the built-in upgrade mechanism<br>php-curl \<br>php-zip \<br># For converting uploaded images to webp<br># (see https://codeberg.org/kevquirk/hooks.pureblog.org/src/branch/main/src/hooks/image-to-webp.md)<br>php-gd \<br># For markdown rendering<br>php-xml \<br># For Pure Comments<br>php-sqlite3 \<br>sqlite3 \<br>apt-get clean && rm -rf /var/lib/apt/lists/*

# Enable Apache modules<br># 'rewrite' is needed for URL routing<br># 'headers' is good practice<br>RUN a2enmod rewrite && \<br>a2enmod headers && \<br># Dynamically enable the installed PHP module (e.g., php8.3)<br>a2enmod $(ls /usr/lib/apache2/modules/ | grep libphp | sed 's/libphp.so//' | sed 's/\.so//') || true

# Configure Apache to allow .htaccess overrides (Crucial for PureBlog routing)<br>RUN sed -i 's/AllowOverride None/AllowOverride All/g' /etc/apache2/apache2.conf && \<br># Add user to run apache as<br>groupadd -g 1000 pureblog && useradd -g 1000 -u 1000 pureblog && \<br>sed -i 's/APACHE_RUN_USER=www-data/APACHE_RUN_USER=pureblog/g' /etc/apache2/envvars && \<br>sed -i 's/APACHE_RUN_GROUP=www-data/APACHE_RUN_GROUP=pureblog/g' /etc/apache2/envvars && \<br># Change Apache listening port<br>sed -i 's/Listen 80/Listen 8080/g' /etc/apache2/ports.conf && \<br># Make sure redirects use port 80<br>sed -i -e 's||\n ServerName bfloeser.de:80\n UseCanonicalName On|' \<br>/etc/apache2/sites-available/000-default.conf && \<br># Set document root<br>sed -i 's/DocumentRoot \/var\/www\/html/DocumentRoot \/var\/www\/pureblog/g' /etc/apache2/sites-available/000-default.conf

WORKDIR /var/www/pureblog<br>COPY --chown=1000:1000 . .

EXPOSE 8080<br>USER 1000

# Start Apache in the foreground<br>CMD ["apache2ctl", "-D", "FOREGROUND"]<br>I'm not going to explain all this in detail. Let me just quickly point out the relevant components:

Install all dependencies for Pure Blog and Pure Comments

Enable some Apache modules

Change default user and port Apache uses

Run Container as non-root

Alongside this, I wrote a compose file for Docker Compose:

services:<br># Run before app startup in case anyone messed with file permissions.<br># This is only needed, if the container user differs from the host<br># user handling the respective files<br>init-permissions:<br>image: busybox<br>user: root<br>volumes:<br>- ./content:/content<br>- ./config:/config<br>- ./data:/data<br>command: |<br>chown -R 1000:1000 /content && \<br>chown -R 1000:1000 /config && \<br>chown -R 1000:1000 /data

pureblog:<br>build:<br># Better always pull, base image is probably updated frequently<br>pull: true<br>context: .<br>dockerfile: Dockerfile<br>container_name: pureblog-apache<br>ports:<br># Make sure the port is only...

blog pure pureblog apache apache2 from

Related Articles