Banner of Setting Up Pelican Like a Developer (Not a Beginner) – Static, But Smarter

Setting Up Pelican Like a Developer (Not a Beginner) – Static, But Smarter


Category: Programming » Web Development

📅 May 06, 2026   |   👁️ Views: 1

Author:   mosaid

Most Pelican tutorials teach the same sequence: run pelican-quickstart, tweak a few settings, and start writing. It works — until it doesn’t.

The failure mode is predictable: as soon as the project outgrows “a few posts”, configuration spreads, Python environments drift, plugins are added opportunistically, and the folder becomes a grab-bag of generated output and source files. The site still builds… right up until the day it doesn’t, and you can’t explain why.

In this article, we’re not “setting up Pelican”. We’re building a reproducible content system:

deterministic environmentclean project scaffoldsplit configurationdisciplined dependenciesautomation from day onea first-run page

If you’re a beginner: you’ll get a path that works now and keeps working later. If you’re a power user: you’ll get a structure you can version, automate, deploy, and extend without regret.


1. Introduction – Why “Beginner” Setups Break

Beginner setups break for one reason: they optimize for getting a page on screen, not for building a maintainable pipeline.

A production-grade static site isn’t “a folder of Markdown” — it’s a system with:

• a pinned interpreter and reproducible dependencies

• a clean separation between source and build output

• configuration that behaves differently in dev vs production

• automation that makes the “right thing” the easiest thing

Let’s build that foundation first.


2. Environment Isolation: Controlled Python, Not Global Chaos

The first rule of a maintainable Pelican setup is simple: your Python environment must be predictable.

We’ll do this in three layers:

• pin the Python interpreter (pyenv)

• isolate dependencies (venv)

• separate “what we want” from “what gets installed” (pip-tools)

2.1 Pin the interpreter with pyenv

If you already manage Python another way (asdf, mise, containers), keep your tool — the goal is still the same: pin the interpreter. Here’s the pyenv approach:

  
pyenv install 3.12.2
pyenv local 3.12.2

That creates .python-version. Anyone cloning your repo gets the exact same Python version.

2.2 Create a local virtual environment

  
python -m venv .venv
source .venv/bin/activate

At this point: no global packages, no “works on my laptop” surprises.

2.3 Use pip-tools for deterministic installs

A common trap is freezing everything too early. Instead, keep two files:

requirements.in = your direct dependencies (intent)

requirements.txt = fully pinned dependency graph (resolution)

  
pip install pip-tools

# Define only direct dependencies
echo "pelican\nmarkdown" > requirements.in

# Compile a fully pinned lockfile
pip-compile requirements.in

# Install exactly what was compiled
pip-sync requirements.txt

Key idea: You’re not setting up “a blog”. You’re defining a build environment.

From now on, your environment is deterministic: Python version locked, dependencies reproducible, no global pollution.


3. The Professional Project Scaffold

The default Pelican layout is minimal. Minimal is fine for a weekend experiment. For a real site, you want a directory structure that makes it obvious what is:

• source

• output

• UI/theme

• extensions

• automation

  
project/
│
├── content/              # source: articles, pages, images, extra files
│   ├── articles/
│   ├── pages/
│   ├── images/
│   └── extra/
├── output/               # generated site (never hand-edit)
├── theme/                # templates + CSS/JS
├── plugins/              # optional, controlled extensions
├── tasks/                # build/deploy scripts (later)
├── tools/                # custom generators (later)
├── pelicanconf.py        # dev config
├── publishconf.py        # production overrides
├── requirements.in       # intent
├── requirements.txt      # lockfile
└── Makefile              # automation

This separation is what turns Pelican into a content pipeline, not just a static site generator.

3.1 Bootstrap the structure (one command)

You can create everything manually, but a bootstrap script gives you repeatability. Keep it simple, and don’t be afraid to parameterize it.

  
#!/usr/bin/env bash
set -euo pipefail

PROJECT_NAME="pelican-project"
PYTHON_VERSION="3.12.2"

echo "[+] Creating project structure..."

mkdir -p "$PROJECT_NAME"/{content/{articles,pages,images,extra},output,theme,plugins,tasks,tools}

touch "$PROJECT_NAME"/pelicanconf.py
touch "$PROJECT_NAME"/publishconf.py
touch "$PROJECT_NAME"/Makefile
touch "$PROJECT_NAME"/requirements.in

touch "$PROJECT_NAME"/content/articles/.keep
touch "$PROJECT_NAME"/content/pages/.keep

echo "[+] Initializing Python environment..."
cd "$PROJECT_NAME"

pyenv local "$PYTHON_VERSION"

python -m venv .venv
source .venv/bin/activate

pip install pip-tools

echo -e "pelican\nmarkdown" > requirements.in
pip-compile requirements.in
pip-sync requirements.txt

echo "[✓] Project ready."

Now your Pelican project can be created in seconds — consistently, every time.


4. Configuration Strategy – Split Dev vs Production (and keep secrets out of git)

Treat configuration like code: explicit, modular, reviewable. Pelican gives you two natural layers:

pelicanconf.py → development defaults

publishconf.py → production overrides

4.1 A clean development config

Keep the baseline boring and stable. This article’s goal is to get structure correct, not to introduce features.

  
# pelicanconf.py

PATH = "content"

TIMEZONE = "UTC"
DEFAULT_LANG = "en"

THEME = "theme"

STATIC_PATHS = ["images", "extra"]
PAGE_PATHS = ["pages"]

# Optional hardening for dev feeds
FEED_ALL_ATOM = None
CATEGORY_FEED_ATOM = None

4.2 Minimal production overrides

  
# publishconf.py

from pelicanconf import *

SITEURL = "https://example.com"
DELETE_OUTPUT_DIRECTORY = True

4.3 Optional: use a .env file (only if you need it)

If you want environment-specific values (different SITEURL, feature flags, etc.), you can use a .env. Just remember: a .env is a convenience, not a requirement.

  
SITEURL=http://localhost:8002

If you do this, add python-dotenv to your dependencies and load it explicitly:

  
echo "python-dotenv" >> requirements.in
pip-compile requirements.in
pip-sync requirements.txt

  
# pelicanconf.py
from dotenv import load_dotenv
load_dotenv()

And crucially: don’t commit secrets. Add .env to .gitignore.


5. Plugins: Intentional Selection (Not “plugin-first”)

Pelican plugins are powerful — and easy to overuse. Every plugin becomes part of your build pipeline. So we follow a strict rule: plugins must earn their place.

In the early stage, you should be able to build a working site with zero plugins. That forces you to understand:

• where data comes from (content + metadata)

• how it flows through templates

• how URLs and structure are generated

When you do add plugins, add them like normal dependencies: pinned, documented, and reproducible.

  
# example: sitemap (only when you’re ready)
pip install pelican-sitemap

Then enable them explicitly (and only where needed):

  
PLUGIN_PATHS = ["plugins"]
PLUGINS = ["sitemap"]

If you need custom behavior, prefer implementing it in tools/ or your theme once you fully understand the pipeline.


6. Version Control Hygiene: What to Commit vs What to Regenerate

A clean Pelican project is defined by what you don’t keep. Generated output, caches, and virtualenvs should never mix with source content.

Here’s a minimal .gitignore that keeps your repo clean:

  
# Python
.venv/
__pycache__/
*.py[cod]

# Pelican
output/
.pelican-cache/

# Environment
.env

# OS / editor
.DS_Store
.vscode/

As a rule of thumb:

Commit: content/, theme/, configs, requirements.in, requirements.txt, Makefile

Never commit: output/, .venv/, caches, generated artifacts

Golden rule: Only source files matter. Everything else can be regenerated.

Optional but useful: add .editorconfig to prevent formatting drift across machines.


7. The Build System: A Makefile from Day One

Before writing content, define how the system runs. Automation isn’t “extra”; it’s how you make good practices frictionless.

  
.PHONY: dev build publish serve clean

PORT ?= 8002

# Live-reload development server
serve:
    pelican -r -l -p $(PORT)

# Deterministic build (uses pelicanconf.py)
build:
    pelican content -s pelicanconf.py -o output

# Production build (uses publishconf.py)
publish:
    pelican content -s publishconf.py -o output

# Serve the generated output directory
serve:
    cd output && python -m http.server $(PORT)

clean:
    rm -rf output __pycache__ .pelican-cache

This gives you a stable interface:

make serve → live reload

make build → local build

make publish → production output

make clean → wipe generated artifacts


8. First Light: “Hello World” Page

Before you write “real” content, prove the pipeline works end-to-end. We’ll create a page (not an article) because it’s the fastest way to validate output without date ordering or taxonomies.


mkdir -p content/pages
cat > content/pages/home.md << 'EOF'
Title: Hello, World
Slug: home

# Hello, World

This page was generated by Pelican.
EOF

Start the development server:


make serve

Open http://localhost:8002 in your browser. If you see “Hello, World”, your environment, config, and directory layout are all aligned.

Congratulations — the machine works. Now we can safely add complexity without ever breaking the “works out of the box” contract.


9. What We Just Built (and Why It Matters)

You now have a build pipeline, not just a blog folder. From here onward, you’ll repeatedly apply a simple three-layer pattern:

Config: tell Pelican what to load and how to structure output

Theme: render that data intentionally (templates, includes, layout)

Metadata conventions: define the shape of the content so it stays queryable at build time

This pattern scales: it keeps the project understandable at 5 posts and at 500.

Next: in the next article we’ll design a clean content architecture — file naming, front matter vocabulary, and a directory strategy that keeps your site navigable and extensible as it grows.



← Stop Thinking at Runtime: The Power User’s Guide to Precomputation in Pelican Building a Modular Banner Generator for Pelican – A Developer's Approach →