How does this blog work?


Some people have asked me how this blog was running after I explained them I turned away from Wordpress in order to get something lighter, more secure and, above all, more practical for the developer that I still am.

My needs were the following: - I should be able to redact an article offline, using a standard text editor; - I would prefer to be able to do so using Markdown, as I am quite used to this syntax; - I am really bad at UI, HTML, CSS and the likes, so the solution should provide a nice style without me having anything to do about it; - Everything should be stored in a git project, as I am fed up with needing to backup an entire database to keep my content safe; - All technical phases should be automated: I just want to redact and push, nothing else.

Technologies used


Pelican takes care of transforming Markdown into a nice HTML blog. It is a tool based on Python, so I know it will be easy for me to debug something if there is a problem that is not handled by the community in the next version.

Github Actions

The content will be sent to a Github private repository, and a continuous integration and deployment pipeline will be activated to generate and deploy the blog website everytime I send a new article.


My domain is handled by OVH and I have an FTP access to the www directory. Github has a secrets management function included in the Actions feature, so I will not need to expose my access. A subdomain called will have to be created.



After creating a private repository called blog-md, I go to the Actions menu and create a workflow that I call Pelican. The content of the actions.yml file is the following:

name: 'Pelican'
on: [push]
    runs-on: ubuntu-latest
      - name: Retrieve source
        uses: actions/checkout@v3
      - name: Install Python
        uses: actions/setup-python@v4
          python-version: '3.10'
      - name: Install Dependencies
        run: pip install pelican[markdown]
        shell: bash
      - name: Build Pelican
        run: pelican ./content/
        shell: bash
      - name: Install LFTP
        run: sudo apt install lftp
        shell: bash
      - name: Load Secrets
        run: echo "machine ${{ secrets.FTPHOSTNAME }} login ${{ secrets.FTPUSERNAME }} password ${{ secrets.FTPPASSWORD }}" > ~/.netrc
      - name: Deploy Output
        run: lftp -e "mirror --parallel=100 -R ./output/ /www/blog-md/" ${{ secrets.FTPHOSTNAME }}
        shell: bash

The content should be self-explanatory for most of the file lines. The pelican command targets the content directory in my repository, where I put the content of the blog (including images in a subdirectory), in order to keep all of it separate to the technology that turns it into a blog, in case I need to change this someday.

In the settings menu of the repository, I include the value of the secrets for my FTP deployment server, and use lftp to send all the files by mirroring the "local" output directory created by the pelican command with the remote /www/blog-md/ directory I created on my OVH FTP content.

Pelican configuration

I create two files in the root directory of my repository, in order to provide Pelican with some guidance on how to generate the content ( and how to manage the deployment of it ( The content of the first file is the following:

AUTHOR = 'JP Gouigoux'
SITENAME = 'Blog JP Gouigoux'

PATH = 'content'

TIMEZONE = 'Europe/Paris'


# Feed generation is usually not desired when developing

# Blogroll
LINKS = (('Pelican', ''),
         ('', ''),
         ('Jinja2', ''),
         ('You can modify those links in your config file', '#'),)

# Social widget
SOCIAL = (('You can add links in your config file', '#'),
          ('Another social link', '#'),)


# Uncomment following line if you want document-relative URLs when developing

Again, there is not much to explain here, particularly since I have mainly kept all defaults options and not even removed the links yet. Same goes for the second file, which is only used when you publish locally:

import os
import sys
from pelicanconf import *

# If your site is available via HTTPS, make sure SITEURL begins with https://

FEED_ALL_ATOM = 'feeds/all.atom.xml'
CATEGORY_FEED_ATOM = 'feeds/{slug}.atom.xml'


By the way, if you start Pelican locally and use its integrated functions to generate the basic boilerplate, you will also get Makefile, requirements.txt and files, which will help you run the blog site before you commit anything.

OVH configuration

After wasting quite some time trying to figure out how to manage the redirection, I finally realized the right way to manage my subdomain exposition was to use the "Multisite" interface (

Then, it was extremely easy to simply associate the subdomain blog from to a redirection to and wait a bit for the association to work. The SSL certificate had to be renewed, but the interface made it quite easy and, again, it was only a question of waiting for a few minutes.


Now everything is set in place, I only have to concentrate on the content of the blog, using tools that I am comfortable with.

VSCode as Markdown editor

I open my VSCode and concentrate only on the content directory, which contains text plus images and nothing else. The Markdown All In One plugin helps by providing the following commands:

Commands to display Markdown preview panel

This way, I get a nice preview of what will be displayed. Simply pasting images in the text content generates an entry like the following:

Default entry when pasting an image

I then simply rename the images, displace them one level down in the images directory and adjust the Markdown link, checking they are still displayed on the preview panel. After that comes the only remaining thing that hangs on the technical side, which is to prefix the image URL with {filename}, the best way I have found, for now, to avoid problems with accessing the images in relative/absolute paths (if someone knows a better way, I am eager to know about it; please email to jp at this domain):

Adjusted entry for image

Sending the content

The rest is as easy a commiting, then pushing:

Commit of article

And a few moments later, the article is online!

Article online