Static site generator for designers. Uses Python and Django templates.


Build Status


Cactus 3 is out!

We're happy to announce Cactus 3. It brings a set of great new features like asset fingerprinting, an asset pipeline, pretty urls, native Mac filesystem events, automatic nameserver configuration, support for multiple deployment backends (Google Sites) and more. Large parts of the code have been rewritten, accompanied by an extensive suite of unit tests. Many thanks to Thomas Orozco and other contributors.

What is Cactus

Cactus is a simple but powerful static website generator using Python and the Django template system. Cactus also makes it easy to develop locally and deploy your site to S3 directly. It works great for company, portfolio, personal, support websites and blogs.

To get a quick overview watch this short video tutorial.

Cactus is based on the idea that most dynamic features on websites these days can be done using Javascript while the actual site can stay static. Static websites are easy to host and typically very fast.

I developed Cactus because I wanted a standard, easy system that designers at Sofa could use to build and deploy fast websites. So typical users would be designers that are tech-savvy, want to use templates, but don't like to mess with setting up django or S3.

Since then it has evolved quite a bit with a plugin system that supports blogging, spriting, versioning and is extensible.

You can find more discussion about static site generators in this Hacker News discussion.


There is also an example blog project included.

Super quick tutorial for the impatient

Install Cactus with the following one liner

sudo easy_install cactus

If you saw no errors, you can now generate a new project

cactus create ~/

To start editing and previewing your site type the following. Then point your browser to localhost:8000 and start editing. Cactus will automatically rebuild your project and refresh your browser on changes.

cd ~/
cactus serve

Once you are ready to deploy your site to S3 you can run the following. You will need your Amazon access keys. If you don't have one yet, read how to get one here.

cactus deploy

Voila. Your website generated by Cactus and hosted on S3!

Extended guide

Creating a new project

You can create a new project by generating a new project structure like this. Make sure the destination folder does not exist yet.

cactus create [path]

If you did not see any errors, the path you pointed to should now look like this.

- .build                Generated site (upload this to your host)
- pages                 Your actual site pages
    - index.html
    - sitemap.xml
    - robots.txt
    - error.html        A default 404 page
- templates             Holds your django templates
    - base.html
- static                Directory with static assets
    - images
    - css
    - js
- plugins               A list of plugins. To enable remove disabled from the name

Making your site

After generating your site you can start building by adding pages to contents, which can rely on templates. So for example if you want a page /articles/2010/my-article.html you would create the file with directories in your pages folder. Then you can edit the file and use django's template features.

Building your site

When you build your site it will generate a static version in the build folder that you can upload to any host. Basically it will render each page from your pages folder, copy it over to the build folder and add all the static assets to it so it becomes a self contained website. You can build your site like this:

cd [your-cactus-path]
cactus build

Your rendered website can now be found in the (hidden) [path]/.build folder. Cactus can also run a small webserver to preview your site and update it when you make any changes. This is really handy when developing to get live visual feedback.

You can run it like this:

cactus serve

Linking and contexts

Cactus makes it easy to relatively link to pages and static assets inside your project by using the template tags {% static %} and {% url %}. For example if you are at page /blog/2011/Jan/my-article.html and would like to link to /contact.html you would write the following:

<a href="{% url '/contact.html' %}">Contact</a>

Just use the URL you would normally use: don't forget the leading slash.


Cactus uses the Django templates. They should be very similar to other templating systems and have some nice capabilities like inheritance. In a nutshell: a variable looks like this {{ name }} and a tag like this {% block title %}Welcome{% endblock %}. You can read the full documentation at the django site.

Enabling Plugins

To enable a plugin for your site, change the file name from [PLUGIN] to [PLUGIN].py.


Cactus can deploy your website directly to S3, all you need are your Amazon credentials and a bucket name. Cactus remembers these in a configuration file name config.json to make future deploys painless. The secret key is stored securely in the Keychain or similar services on other OSs.

cactus deploy

After deploying you can visit the website directly. Cactus also makes sure all your text files are compressed and adds caching headers.



For the full example of how to build a blog on top of Cactus, see CactusBlog.

Blog plugin takes post title, author, and date from metadata. For example:

title: My first post
author: Koen Bok
date: 22-07-2012

{% extends "post.html" %}
{% block body %}

{% endblock %}

Modify config.json to set a custom blog path, default author name, or date pattern used to parse metadata. The defaults are:

"blog": {
    "path": "blog",
    "author": "Unknown",
    "date-format": "%d-%m-%Y"

YAML Variables

By default you can declare variables to be included above each page, for example:

test_text: Lorem Ipsum

<p>{{ test_text }}</p>

You can declare the variables using YAML instead. Just surround the block with the --- and ... Document Separators. Then the objects and arrays will be available inside the templates:

header_text: Lorem Ipsum
  name: Lorem
  description: Ipsum
    name: lorem
    name: ipsum

{% for item in custom_array %}
  <p>{{ header_text }}: {{ }}</p>
{% endfor %}

<p>{{ }} | {{ custom_object.description }}</p>

The PyYAML library is used for this functionality.

Asset pipeline

Cactus comes with an asset pipeline for your static files. If you'd like to use it, make sure you use the {% static %} template tag to link to your static assets: they might be renamed in the process.


Modify config.json, and add the extensions you want to be fingerprinting:

"fingerprint": [

This lets you enable caching with long expiration dates. When a file changes, its name will reflect the change. Great for when you use a CDN.


Modify config.json, and add the extensions you want to be optimizing:

"optimize": [

By default, Cactus will use:

  • YUI for CSS minification
  • Closure compiler for JS minification (YUI is built-in too, so you can use it!)

Check out plugins/ in your project to understand how this works. It's very easy to add your own optimizers!

Site URL

If you would like for your sitemap to have absolute paths you need to add a site-url key to your config.json

You can enable this by adding modifying your configuration and adding:

"site-url": "",

Note that you need to do this if you want your sitemap to be valid for Google Webmaster Tools.

"Pretty" URLs

If you would like to not have ".html" in your URLs, Cactus can rewrite those for you, and make "/my-page.html" look appear as "/my-page/", by creating the "/my-page/index.html" file.

You can enable this by adding modifying your configuration and adding:

"prettify": true

Note that if you're going to use this, you should definitely set your "Meta canonical" to the URL you're using so as to not hurt your search rankings:

<link rel="canonical" href="{{ CURRENT_PAGE.absolute_final_url }}" />

Nameserver configuration

To set up a hosted zone and generate the correct nameserver records for your domain, make sure your bucket is a valid domain name, and run:

cactus domain:setup

Cactus will return with a set of nameservers that you can then enter with your registrar. To see the list again run:

cactus domain:list

If your domain is 'naked' (eg. without www), Cactus will add create an extra bucket that redirects the www variant of your domain to your naked domain (so to All the above is Amazon only for now.

Extra files

Cactus will auto generate a robots.txt and sitemap.xml file for you based on your pages.

This will help bots to index your pages for Google and Bing for example.

Python Versions

Cactus is tested on Python 2.6, 2.7, 3.4 and 3.5. It probably works on Python 3.3 as well.

  • Can't deploy

    Can't deploy

    Stack trace

    jessie at debian in ~/jessieland on master [!]
    $ cactus deploy
    Plugins: version, blog
    /usr/local/lib/python2.7/site-packages/django/contrib/markup/templatetags/ DeprecationWarning: The markdown filter has been deprecated
    Building error.html
    Building index.html
    Building posts/how-to-make-foursquare-your-bitch.html
    Building posts/linux-on-mac.html
    Building posts/what-would-2pac-do.html
    Building robots.txt
    Building sitemap.xml
    Amazon secret access key (will be saved in keychain): MY_TOKEN
    sh: 1: security: not found
    S3 bucket name (
    Traceback (most recent call last):
      File "/usr/local/bin/cactus", line 9, in <module>
        load_entry_point('Cactus==2.3.1', 'console_scripts', 'cactus')()
      File "/usr/local/lib/python2.7/site-packages/cactus/", line 90, in main
      File "/usr/local/lib/python2.7/site-packages/cactus/", line 42, in deploy
      File "/usr/local/lib/python2.7/site-packages/cactus/", line 335, in upload
        self.config.set('aws-bucket-website', self.bucket.get_website_endpoint())
      File "/usr/local/lib/python2.7/site-packages/boto/s3/", line 1558, in get_website_endpoint
      File "/usr/local/lib/python2.7/site-packages/boto/s3/", line 1133, in get_location
      File "/usr/local/lib/python2.7/site-packages/boto/s3/", line 664, in make_request
      File "/usr/local/lib/python2.7/site-packages/boto/", line 1070, in make_request
      File "/usr/local/lib/python2.7/site-packages/boto/", line 942, in _mexe
        request.body, request.headers)
      File "/usr/local/lib/python2.7/", line 1001, in request
        self._send_request(method, url, body, headers)
      File "/usr/local/lib/python2.7/", line 1035, in _send_request
      File "/usr/local/lib/python2.7/", line 997, in endheaders
      File "/usr/local/lib/python2.7/", line 850, in _send_output
      File "/usr/local/lib/python2.7/", line 812, in send
      File "/usr/local/lib/python2.7/", line 1212, in connect
      File "/usr/local/lib/python2.7/", line 350, in wrap_socket
      File "/usr/local/lib/python2.7/", line 566, in __init__
      File "/usr/local/lib/python2.7/", line 796, in do_handshake
        match_hostname(self.getpeercert(), self.server_hostname)
      File "/usr/local/lib/python2.7/", line 269, in match_hostname
        % (hostname, ', '.join(map(repr, dnsnames))))
    ssl.CertificateError: hostname u'' doesn't match either of '*', ''
    opened by jessfraz 13
  • up to date blog example?

    up to date blog example?

    hi, just evaluating cactus and seems it could be just what i was looking for.

    is there an up to date example of using the blog plugin anywhere, one that uses the new/current templates and plays well with having "prettify" turned on in config.json??


    opened by elguavas 11
  • Maintaining Cactus

    Maintaining Cactus

    On the Cactus Blog Jorn says:

    The Cactus core is open source, so please keep contributing to the project. We've stopped active development but will maintain the current feature set

    I see a lot of open Issues/Pull Requests that have not had answers or follow ups in a while. Are there still some maintainers around to respond to and accept PRs?

    opened by 153957 11
  • Makes full template power available to blog posts.

    Makes full template power available to blog posts.

    Previously, I got weired errors when using e.g. {% url path %} in blog posts:

    *** Error while building
    Traceback (most recent call last):
    File "/usr/local/lib/python2.7/dist-packages/Cactus-3.3.2-py2.7.egg/cactus/", line 52, in url
        site = context['__CACTUS_SITE__']
      File "/usr/local/lib/python2.7/dist-packages/Django-1.6.11-py2.7.egg/django/template/", line 56, in __getitem__
        raise KeyError(key)
    KeyError: '__CACTUS_SITE__'

    It turns out that getNode needs the full site context to work probably. Therefore this commit creates the site context and adds it to all postContexts. Now I can use {% url path %} and the likes in my blog posts.

    opened by blattms 9
  • Django variable CURRENT_PAGE.absolute_final_url not existing in Cactus 1.1.22 (440)

    Django variable CURRENT_PAGE.absolute_final_url not existing in Cactus 1.1.22 (440)

    I prettified my URLs and tried to set my "Meta canonical" according to the using

    <link rel="canonical" href="{{ CURRENT_PAGE.absolute_final_url }}" />

    This gives me:

    <link rel="canonical" href="" />

    Seems the Django variable is not existing.

    opened by rob-weiss 8
  • Failed to load FSEventsListener

    Failed to load FSEventsListener

    I installed cactus via pip on my windows machine without any problems. I'm using Python 3.4.3.

    But when i try cactus create my-awe-site I get the following error message:

    Failed to load FSEventsListener Traceback (most recent call last): File "c:\users\username\envs\cactusenv\lib\site-packages\cactus\listener\", line 11, in <module> from cactus.listener.mac import FSEventsListener as Listener File "c:\users\username\envs\cactusenv\lib\site-packages\cactus\listener\", line 12, in <module> from fsevents import Observer, Stream ImportError: No module named 'fsevents'

    my-awe-site folder get created, but when i run cactus serve, I get the following:

    Failed to load FSEventsListener Traceback (most recent call last): File "c:\users\username\envs\cactusenv\lib\site-packages\cactus\listener\", line 11, in <module> from cactus.listener.mac import FSEventsListener as Listener File "c:\users\username\envs\cactusenv\lib\site-packages\cactus\listener\", line 12, in <module> from fsevents import Observer, Stream ImportError: No module named 'fsevents' Running webserver at for D:\cloud\Projects\PyProjects\projects\my-awe-site\.build Type control-c to exit Traceback (most recent call last): File "c:\python34\Lib\", line 170, in _run_module_as_main "__main__", mod_spec) File "c:\python34\Lib\", line 85, in _run_code exec(code, run_globals) File "C:\Users\username\Envs\cactusenv\Scripts\cactus.exe\", line 9, in <module> File "c:\users\username\envs\cactusenv\lib\site-packages\cactus\", line 143, in main**{k: v for k, v in vars(args).items() if k != 'target'}) File "c:\users\username\envs\cactusenv\lib\site-packages\cactus\", line 66, in serve site.serve(port=port, browser=browser) File "c:\users\username\envs\cactusenv\lib\site-packages\cactus\", line 449, in serve self.server = WebServer(self.build_path, port=port) File "c:\users\username\envs\cactusenv\lib\site-packages\cactus\", line 85, in __init__ self.path = path.decode("utf-8") AttributeError: 'str' object has no attribute 'decode'

    opened by rfmokoena 8
  • Support URLs that don't start with `/static/`

    Support URLs that don't start with `/static/`

    In a TODO says exactly that.

    I'd like to tackle this one, as we need it in one of our projects.

    Do you have any hints on how to do this? Introduce a config setting, and if present use that as a base url for static assets? Did you have any concrete plans/ideas on how to implement this?

    I wonder if it would be a good idea to reuse django's STATIC_ROOT related capabilities (or rather the collectstatic contrib to be exact) for that.

    But this would somehow contradict the item on the roadmap (see #117) which states/asks, that other template engines than Django's would be a nice feature.

    opened by benjmin-r 7
  • jinja2 templates?

    jinja2 templates?

    hi again, last question for now ;) .

    it seems sad that cactus pulls in all of (a very old 1.5) version of django just to use the templating engine. is it possible that cactus will eventually support, for example, jinja2 templates, which django itself now officially supports as of version 1.8?

    seems like it would be much lighter just to rely on a modern templating engine rather than a (currently ancient) version of django.

    just general questions, no expectations. i'm an open source developer myself and i have a full understanding of the time constraints of volunteer developers.


    opened by elguavas 7
  • Could not call YUICSSOptimizer/ClosureJSOptimizer

    Could not call YUICSSOptimizer/ClosureJSOptimizer

    When I run cactus build or cactus serve, I get errors related to my use of optimize in config.json:

    Could not call external processor YUICSSOptimizer: [Errno 2] No such file or directory
    Could not call external processor ClosureJSOptimizer: [Errno 2] No such file or directory

    When I load up a console, importing them like static/ does loads them no problem, so I don't know what I'm doing wrong.

    >>> from cactus.contrib.external.yui import YUICSSOptimizer`

    I'm using the v3 branch.

    My config.json:

        "fingerprint": [
        "optimize": [
        "prettify": true
    opened by richardcornish 7
  • Use prettified URL for blog posts.

    Use prettified URL for blog posts.

    When setting prettify_urls to True, Page.path is not the URL that the page is uploaded to but the one where the source of the page is (including a possible .html extension). Therefore this commit uses Page.final_url which does not have the bogus .html extension when using prettified URLs.

    opened by blattms 6
  • post.path with pretty urls

    post.path with pretty urls

    HI, looking for an idea on the correct way to work around an issue I have when using the pretty urls and calling /{{ post.path }} my posts are html files and so the resulting url I get back is "/posts/foo.html" which will display "Sorry the page /posts/foo.html could not be found on this server."

    So essentially my issue is that in a page called "posts.html" (rendered url is /posts/) I am doing something like:

    {% for post in posts %} <a href="/{{ post.path }}">{{post.title}}</a> {% endfor %}

    and getting the message "Sorry the page /posts/foo.html could not be found on this server."

    Any ideas on how I dynamically pull the post.path to return something more like /posts/foo/, if I manually remove .html from the url the page will render correctly.

    Hopefully this isn't a stupid question...

    opened by jonsaul 6
  • Improve Python3 compatibility; update Django to 1.11

    Improve Python3 compatibility; update Django to 1.11

    This PR updates Django to 1.11 (latest version supporting Python2.7) and removes Python3 deprecation warnings (#280).

    It maintains Python2.7 compatibility, although it might be a good idea to drop Python2.7 support altogether...

    opened by m-thielen 0
  • docs: fix simple typo, requirments -> requirements

    docs: fix simple typo, requirments -> requirements

    There is a small typo in

    Should read requirements rather than requirments.

    Semi-automated pull request generated by

    opened by timgates42 0
  • cactus build fails with Python3

    cactus build fails with Python3

    Build now fails on the deafult skeleton: $ cactus build

    lib/python3.8/site-packages/cactus/utils/ DeprecationWarning: inspect.getargspec() is deprecated since Python 3.0, use inspect.signature() or inspect.getfullargspec()

    opened by plum 3
  • [question] New release?

    [question] New release?

    Any chance a new release off of master will be done? In particular, support for YAML that was introduced with #256 would be great to have released, especially since the README currently references this feature but anyone installing from PyPi will not be able to use it. Early example of this confusion is in #260.

    opened by radusuciu 1
  • Pretty URLs on S3 with HTTPS

    Pretty URLs on S3 with HTTPS

    opened by jnymck 0
  • Testing site before uploading

    Testing site before uploading

    I'm using the patch to allow hosting a site from a non-root location (by adding vars to config.json). But now that I've done this, I can no longer do local testing, because URLs that used to be '/static/...' are now 'base_path/static/...' and so I can't test locally using cactus serve. Is there a work around I can use?

    opened by geomblog 2
Simple, lightweight, and magic-free static site/blog generator for Python coders Take full control of your static website/blog generation by writing your own simple, lightweight, and magic-free static site generator in

Sunaina Pai 1.7k Jan 1, 2023
Static site generator that supports Markdown and reST syntax. Powered by Python.

Pelican Pelican is a static site generator, written in Python. Write content in reStructuredText or Markdown using your editor of choice Includes a si

Pelican dev team 11.3k Jan 4, 2023
AutoLoader is a plugin for Pelican, a static site generator written in Python.

AutoLoader AutoLoader is a plugin for Pelican, a static site generator written in Python. AutoLoader is designed to autoload the other Pelican plugins

null 2 Nov 7, 2022
Kaktos is a python static site generator

Python static site generator κάκτος Kaktos is a python static site generator. The idea is create a simple static site generator for people that don't

Paulo Coutinho 4 Sep 21, 2022
barely is a lightweight, but highly extensible static site generator written in pure python.

barely is a lightweight, but highly extensible static site generator. Explore the docs » Quickstart · See available Plugins · Report Bug · Request Fea

null 40 Dec 1, 2022
A simple static site generator with deployment to S3/Cloudfront.

Stasis A simple static site generator with deployment to S3/Cloudfront. Features Stasis is a static website generator written in Python, using Pandoc

Scott Czepiel 56 Sep 29, 2022
dirmaker is a simple, opinionated static site generator for quickly publishing directory websites.

dirmaker is a simple, opinionated static site generator for publishing directory websites (eg:, It takes entries from a YAML file and generates a categorised, paginated directory website.

Kailash Nadh 40 Nov 20, 2022
Simple Static Site Inductor Made in Python

sssimp ?? Simple Static Site Inductor Made in Python How to use Create a folder called input, inside create a folder called content and an empty file

Tina 11 Oct 9, 2022
A Python Static Website Generator

Version 0.8.9 Overview Hyde starter kit by merlinrebrovic is a really nice way to get started with hyde. Hyde layout for bootstrap by auzigog is also

Hyde - Static Website Generator 1.6k Jan 1, 2023
A static website and blog generator

Nikola, a Static Site and Blog Generator In goes content, out comes a website, ready to deploy. Why Static Websites? Static websites are safer, use fe

Nikola, a static site generator 2.4k Jan 5, 2023
a static website generator to make beautiful customizable pictures galleries that tell a story

Prosopopee Prosopopee. Static site generator for your story. Make beautiful customizable pictures galleries that tell a story using a static website g

Bram 259 Dec 19, 2022
A static website generator for people who enjoy the simpler things in life.

A static website generator for people who enjoy the simpler things in life.

Darren Mulholland 93 Dec 22, 2022
Hobby Project. A Python Library to create and generate static web pages using just python.

PyWeb ??️ ?? Current Release: 0.1 A Hobby Project ?? PyWeb is a small Library to generate customized static web pages using python. Aimed for new deve

Abhinav Sinha 2 Nov 18, 2021
The lektor static file content management system

Lektor Lektor is a static website generator. It builds out an entire project from static files into many individual HTML pages and has a built-in admi

Lektor CMS 3.6k Dec 29, 2022
Makes dynamic linked shit "static". Amazing What does it do? You give it a dynamically linked binary and it will make a directory that has all the dependencies (recursively). It also f

Stephen Tong 24 Dec 16, 2022
A declarative website generator designed for high-quality websites, with a focus on easy maintenance and localization.

Grow Grow is a declarative tool for rapidly building, launching, and maintaining high-quality static HTML. Easy installation Jinja template engine Con

Grow 385 Dec 3, 2022
A Python media index

pyvideo is simply an index of Python-related media records. The raw data being used here comes out of the pyvideo/data repo. Befor

pyvideo 235 Dec 24, 2022
Django-static-site - A simple content site framework that harnesses the power of Django without the hassle

coltrane A simple content site framework that harnesses the power of Django with

Adam Hill 57 Dec 6, 2022
A python-based static site generator for setting up a CV/Resume site

ezcv A python-based static site generator for setting up a CV/Resume site Table of Contents What does ezcv do? Features & Roadmap Why should I use ezc

Kieran Wood 5 Oct 25, 2022
Scan Site - Tools For Scanning Any Site and Get Site Information

Site Scanner Tools For Scanning Any Site and Get Site Information Example Require - pip install colorama - pip install requests How To Use Download Th

NumeX 5 Mar 19, 2022