Ghost instead of WordPress – Setup a self-hosted Custom Theme

WordPress is powerful. Maybe too powerful. If all you want is a clean blog or a product landing page, you quickly find yourself fighting plugin sprawl, sluggish load times, and an admin interface designed for agencies – not for people who just want to write.

I looked around for alternatives and landed on Ghost. Open source, MIT license, modern editor, and most importantly: no overhead. Here is how I set it up.

Running Ghost with Docker

The only prerequisite is Docker – no PHP, no local Node.js, nothing else. A simple docker-compose.yml is all it takes:

services:
  ghost:
    image: ghost:latest
    ports:
      - "2368:2368"
    environment:
      url: http://localhost:2368
      database__client: sqlite3
      database__connection__filename: /var/lib/ghost/content/data/ghost.db
      privacy__useMinimalPasswordStrength: true
    volumes:
      - ghost_data:/var/lib/ghost/content
      - ./themes/my-theme:/var/lib/ghost/content/themes/my-theme

volumes:
  ghost_data:
docker compose up -d

Done. Ghost runs on http://localhost:2368, the admin panel at http://localhost:2368/ghost.

One note on the password policy: Ghost enforces a minimum password strength. For local testing, something like Localtest1234 does the job – no special characters needed. And honestly, length beats complexity anyway.

Customizing the Theme

Ghost ships with the Casper theme – minimalist and solid. I used it as a base and copied it directly from the running container:

docker cp $(docker ps -q -f ancestor=ghost):/var/lib/ghost/current/content/themes/casper ./themes/my-theme

The theme now lives locally and is mounted via Docker volume. Changes are reflected immediately.

A Custom Homepage as a One-Pager

By default Ghost shows the blog post list on the homepage. I wanted a product landing page at the top – with the latest posts below it.

Ghost handles this elegantly: if the theme contains a file called home.hbs, it is automatically used for the homepage. Everything else stays untouched.

The trick: don’t hardcode the homepage content into the template. Instead, maintain it as a Ghost Page in the editor – that way it can be updated anytime without touching the theme.

Create a new page in the admin panel with the slug home, write your content, and load it in home.hbs using the {{#get}} helper:

{{!< default}}

{{!-- Cover Image --}}
<div class="site-header-content outer">
    {{#if @custom.show_publication_cover}}
        {{#if @site.cover_image}}
            <img class="site-header-cover"
                srcset="{{img_url @site.cover_image size="s"}} 300w,
                        {{img_url @site.cover_image size="m"}} 600w,
                        {{img_url @site.cover_image size="l"}} 1000w,
                        {{img_url @site.cover_image size="xl"}} 2000w"
                sizes="100vw"
                src="{{img_url @site.cover_image size="xl"}}"
                alt="{{@site.title}}"
            />
        {{/if}}
    {{/if}}
    <div class="site-header-inner inner">
        <h1 class="site-title">{{@site.title}}</h1>
        {{#if @site.description}}
            <p class="site-description">{{@site.description}}</p>
        {{/if}}
    </div>
</div>

{{!-- Load content from the "home" page --}}
{{#get "pages" filter="slug:home"}}
  {{#foreach pages}}
    <div class="outer">
      <h2 class="inner">{{title}}</h2>
    </div>
    <section class="gh-content gh-canvas">
      {{content}}
    </section>
  {{/foreach}}
{{/get}}

{{!-- Blog Posts --}}
<main id="site-main" class="site-main outer">
  <div class="inner posts">
    <div class="post-feed">
      {{#foreach posts}}
        {{> "post-card"}}
      {{/foreach}}
    </div>
    {{pagination}}
  </div>
</main>

One important detail: use {{content}} instead of {{{html}}} – only the official Ghost helper correctly renders Full-Width and Wide elements from the editor.

Conclusion

Ghost is what WordPress could have been if it were built today. Clean, fast, and focused. The editor is a pleasure to use, the theme architecture is easy to understand, and Docker makes the whole setup reproducible and portable.

For a personal blog or a product website, I would choose Ghost again without hesitation.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.