<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Ghost-Tag on MoskitoHero</title><link>/tags/ghost-tag/</link><description>Recent content in Ghost-Tag on MoskitoHero</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Tue, 06 May 2025 00:00:00 +0000</lastBuildDate><atom:link href="/tags/ghost-tag/index.xml" rel="self" type="application/rss+xml"/><item><title>Set up Ghost with Podman on a VPS</title><link>/post/2025-05-06-set-up-ghost-with-podman-on-a-vps/</link><pubDate>Tue, 06 May 2025 00:00:00 +0000</pubDate><guid>/post/2025-05-06-set-up-ghost-with-podman-on-a-vps/</guid><description>A step-by-step walkthrough for running Ghost on a VPS using Podman quadlets backed by Systemd, with MySQL and Caddy as a reverse proxy.</description><content:encoded><![CDATA[<div class="paragraph">
<p>I recently moved this website / blog from Jekyll to <a href="https://ghost.org/">Ghost</a>, just to try it out after hearing quite a lot of nice buzz about it lately.
I had this small personal VPS at hand, so I decided to give it some space there, and use <a href="https://podman.io/">Podman</a> (and its <em>quadlets</em> feature) to set it up. This leverages Systemd to run your docker images. Here is a quick walkthrough, should anyone be interested:
Obviously, you need a VPS with SSH access. It must have its firewall open to the web on port 443. My VPS is running Ubuntu, so my install commands will reflect this. I also chose to use root mode to run the quadlets, but there are other options available.</p>
</div>
<div class="sect2">
<h3 id="setup"><a class="anchor" href="#setup"></a>Setup</h3>
<div class="paragraph">
<p>First, <a href="https://podman.io/docs/installation">install Podman</a>:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="nb">sudo </span>apt <span class="nb">install </span>podman podman-docker podman-toolbox</code></pre>
</div>
</div>
<div class="paragraph">
<p>Pull the required images from docker:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="nb">sudo </span>podman pull docker.io/mysql:8
<span class="nb">sudo </span>podman pull docker.io/ghost:5-alpine
<span class="nb">sudo </span>podman pull docker.io/caddy:alpine</code></pre>
</div>
</div>
</div>
<div class="sect1">
<h2 id="the-systemd-services"><a class="anchor" href="#the-systemd-services"></a>The Systemd services</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Create a MySQL container file at /etc/containers/systemd/mysql.container with this content:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="o">[</span>Unit]
<span class="nv">Description</span><span class="o">=</span>Blog MySQL Container
<span class="nv">After</span><span class="o">=</span>network-online.target

<span class="o">[</span>Container]
<span class="nv">ContainerName</span><span class="o">=</span>blog-mysql
<span class="nv">AddCapability</span><span class="o">=</span>SYS_NICE
<span class="nv">Image</span><span class="o">=</span>docker.io/mysql:8
<span class="nv">Volume</span><span class="o">=</span>/srv/containers/blog/mysql/var/lib/mysql:/var/lib/mysql:Z
<span class="nv">Pod</span><span class="o">=</span>blog.pod
<span class="nv">Secret</span><span class="o">=</span>blog_db_name,type<span class="o">=</span><span class="nb">env</span>,target<span class="o">=</span>MYSQL_DATABASE
<span class="nv">Secret</span><span class="o">=</span>blog_db_user,type<span class="o">=</span><span class="nb">env</span>,target<span class="o">=</span>MYSQL_USER
<span class="nv">Secret</span><span class="o">=</span>blog_db_password,type<span class="o">=</span><span class="nb">env</span>,target<span class="o">=</span>MYSQL_PASSWORD
<span class="nv">Secret</span><span class="o">=</span>blog_db_rootpassword,type<span class="o">=</span><span class="nb">env</span>,target<span class="o">=</span>MYSQL_ROOT_PASSWORD

<span class="o">[</span>Service]
<span class="nv">Restart</span><span class="o">=</span>always

<span class="o">[</span>Install]
<span class="nv">WantedBy</span><span class="o">=</span>default.target
<span class="nv">RequiredBy</span><span class="o">=</span>blog-ghost.service</code></pre>
</div>
</div>
<div class="paragraph">
<p>Create a Ghost container file at /etc/containers/systemd/ghost.container
Do not forget to change yourdomain.com with your domain.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="o">[</span>Unit]
<span class="nv">Description</span><span class="o">=</span>Ghost Container
<span class="nv">After</span><span class="o">=</span>blog-db.service

<span class="o">[</span>Container]
<span class="nv">ContainerName</span><span class="o">=</span>blog-ghost
<span class="nv">Environment</span><span class="o">=</span><span class="nv">database__client</span><span class="o">=</span>mysql <span class="nv">database__connection__host</span><span class="o">=</span>blog-mysql <span class="nv">url</span><span class="o">=</span>http://yourdomain.com
<span class="nv">Image</span><span class="o">=</span>docker.io/ghost:5-alpine
<span class="nv">Pod</span><span class="o">=</span>blog.pod
<span class="nv">Secret</span><span class="o">=</span>blog_db_name,type<span class="o">=</span><span class="nb">env</span>,target<span class="o">=</span>database__connection__database
<span class="nv">Secret</span><span class="o">=</span>blog_db_user,type<span class="o">=</span><span class="nb">env</span>,target<span class="o">=</span>database__connection__user
<span class="nv">Secret</span><span class="o">=</span>blog_db_password,type<span class="o">=</span><span class="nb">env</span>,target<span class="o">=</span>database__connection__password
<span class="nv">Volume</span><span class="o">=</span>/srv/containers/blog/var/lib/ghost/content:/var/lib/ghost/content:Z

<span class="o">[</span>Service]
<span class="nv">Restart</span><span class="o">=</span>always

<span class="o">[</span>Install]
<span class="nv">WantedBy</span><span class="o">=</span>default.target</code></pre>
</div>
</div>
<div class="paragraph">
<p>You need to set up the secrets used in these two files to access MySQL. Let’s do it:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="nb">printf</span> <span class="si">$(</span><span class="nb">tr</span> <span class="nt">-dc</span> A-Za-z0-9 &lt;/dev/urandom | <span class="nb">head</span> <span class="nt">-c</span> 13<span class="si">)</span> | <span class="nb">sudo </span>podman secret create blog_db_rootpassword -
<span class="nb">printf</span> <span class="si">$(</span><span class="nb">tr</span> <span class="nt">-dc</span> A-Za-z0-9 &lt;/dev/urandom | <span class="nb">head</span> <span class="nt">-c</span> 13<span class="si">)</span> | <span class="nb">sudo </span>podman secret create blog_db_password -
<span class="nb">printf</span> <span class="s2">&#34;gdb&#34;</span> | <span class="nb">sudo </span>podman secret create blog_db_name -
<span class="nb">printf</span> <span class="s2">&#34;guser&#34;</span> | <span class="nb">sudo </span>podman secret create blog_db_user -</code></pre>
</div>
</div>
<div class="paragraph">
<p>You also need to have the files stored on your filesystem, so let’s create the directories that are mounted in the images:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="nb">sudo mkdir</span> <span class="nt">-p</span> /srv/containers/blog/var/lib/ghost/content
<span class="nb">sudo mkdir</span> <span class="nt">-p</span> /srv/containers/blog/mysql/var/lib/mysql</code></pre>
</div>
</div>
<div class="paragraph">
<p>Now, add a blog.network file at /etc/containers/systemd/blog.network</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="o">[</span>Unit]
<span class="nv">Description</span><span class="o">=</span>Custom blog Podman network

<span class="o">[</span>Network]
<span class="nv">NetworkName</span><span class="o">=</span>blog
<span class="nv">Driver</span><span class="o">=</span>bridge</code></pre>
</div>
</div>
<div class="paragraph">
<p>And a blog.pod file at /etc/containers/systemd/blog.pod</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="o">[</span>Unit]
<span class="nv">Description</span><span class="o">=</span>Blog Pod

<span class="o">[</span>Pod]
<span class="nv">PodName</span><span class="o">=</span>blog
<span class="nv">Network</span><span class="o">=</span>blog.network
<span class="nv">PublishPort</span><span class="o">=</span>3001:2368</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="caddy-set-up"><a class="anchor" href="#caddy-set-up"></a>Caddy set up</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Let’s use <a href="https://caddyserver.com/">Caddy</a> to serve the app to the internet. <em>I chose to leave Caddy out of the blog pod context because I use it to proxy other toy servers on that VPS.</em>
Create the service file at /etc/containers/systemd/caddy.container</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="o">[</span>Container]
<span class="nv">ContainerName</span><span class="o">=</span>caddy
<span class="nv">Image</span><span class="o">=</span>docker.io/caddy:alpine
<span class="nv">PublishPort</span><span class="o">=</span>80:80
<span class="nv">PublishPort</span><span class="o">=</span>443:443
<span class="nv">Volume</span><span class="o">=</span>/srv/containers/caddy/Caddyfile:/etc/caddy/Caddyfile:ro
<span class="nv">Volume</span><span class="o">=</span>/srv/containers/caddy/data:/data
<span class="nv">Volume</span><span class="o">=</span>/srv/containers/caddy/config:/config
<span class="nv">Network</span><span class="o">=</span>host

<span class="o">[</span>Service]
<span class="nv">Restart</span><span class="o">=</span>always

<span class="o">[</span>Install]
<span class="nv">WantedBy</span><span class="o">=</span>default.target</code></pre>
</div>
</div>
<div class="paragraph">
<p>And create the Caddy directory:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="nb">sudo mkdir</span> <span class="nt">-p</span> /srv/containers/caddy</code></pre>
</div>
</div>
<div class="paragraph">
<p>Add a Caddyfile at /srv/containers/caddy/Caddyfile with the following content:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="o">{</span>
    <span class="c"># email to use on Let&#39;s Encrypt</span>
    email your@email.com
<span class="o">}</span>

www.yourdomain.com <span class="o">{</span>
        redir https://yourdomain.com<span class="o">{</span>uri<span class="o">}</span>
<span class="o">}</span>

yourdomain.com <span class="o">{</span>
  reverse_proxy 127.0.0.1:3001
<span class="o">}</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Do not forget to update the file with your email and domain.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="go-live"><a class="anchor" href="#go-live"></a>Go live</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Now let’s open the gates…​</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="nb">sudo </span>systemctl daemon-reload
<span class="nb">sudo </span>systemctl <span class="nb">enable </span>caddy
<span class="nb">sudo </span>systemctl start caddy
<span class="nb">sudo </span>systemctl <span class="nb">enable </span>blog-pod
<span class="nb">sudo </span>systemctl start blog-pod</code></pre>
</div>
</div>
<div class="paragraph">
<p>And visit yourdomain.com/ghost to start blogging!</p>
</div>
</div>
</div>
]]></content:encoded></item></channel></rss>