<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Marin</title>
    <description>The latest articles on DEV Community by Marin (@marin84719).</description>
    <link>https://dev.to/marin84719</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3396758%2Ffc3c3d4c-a261-40fc-84ba-c4bf2b3612c0.png</url>
      <title>DEV Community: Marin</title>
      <link>https://dev.to/marin84719</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/marin84719"/>
    <language>en</language>
    <item>
      <title>Recyclarr + Docker: Easy TRaSH-Guides Sync for Your Media Server</title>
      <dc:creator>Marin</dc:creator>
      <pubDate>Thu, 11 Sep 2025 17:49:23 +0000</pubDate>
      <link>https://dev.to/marin84719/recyclarr-docker-easy-trash-guides-sync-for-your-media-server-24d3</link>
      <guid>https://dev.to/marin84719/recyclarr-docker-easy-trash-guides-sync-for-your-media-server-24d3</guid>
      <description>&lt;p&gt;If you’re running a homelab media server, you’ve probably used Radarr and Sonarr to automate your movie and TV libraries. But keeping them tuned with the latest recommended quality profiles and naming standards from TRaSH-Guides can be a chore. That’s where &lt;strong&gt;Recyclarr&lt;/strong&gt; comes in, it automatically syncs your Radarr and Sonarr settings with TRaSH-Guides so your setup stays clean and consistent. &lt;/p&gt;

&lt;p&gt;This guide assumes you already have a media server stack (e.g., Sonarr/Radarr + Docker) up and running.&lt;/p&gt;

&lt;h2&gt;
  
  
  📑 Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt; 🤔 What is Recyclarr? &lt;/li&gt;
&lt;li&gt; 🛠️ Docker Compose: config setup &lt;/li&gt;
&lt;li&gt; 📦 Configuration basics (&lt;code&gt;recyclarr.yml&lt;/code&gt;) &lt;/li&gt;
&lt;li&gt; 💾 Optional: Customizing Recyclarr &lt;/li&gt;
&lt;li&gt; ✅ Final Thoughts &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="what-is-recyclarr"&gt; 🤔 What is Recyclarr? (and why run it in Docker)&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/recyclarr/recyclarr" rel="noopener noreferrer"&gt;Recyclarr&lt;/a&gt; is an open-source tool that keeps your &lt;a href="https://sonarr.tv/" rel="noopener noreferrer"&gt;Sonarr&lt;/a&gt; and &lt;a href="https://radarr.video/" rel="noopener noreferrer"&gt;Radarr&lt;/a&gt; settings in sync with the latest recommendations from &lt;a href="https://trash-guides.info/" rel="noopener noreferrer"&gt;TRaSH-Guides&lt;/a&gt;. Instead of manually copying quality profiles, custom formats, and naming schemes, Recyclarr automatically pulls the newest configs from TRaSH-Guides and applies them to your apps. That means less time tweaking settings and more time enjoying a clean, consistent media library.&lt;/p&gt;

&lt;p&gt;Updates happen on two levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The tool itself&lt;/strong&gt;: updated by the Recyclarr project (easy to pull new versions via Docker).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The configs&lt;/strong&gt;: fetched from TRaSH-Guides every time you run a sync, so you always get the latest community best practices.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Running Recyclarr in Docker makes it even easier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploy with a single &lt;code&gt;docker compose up -d&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Update by pulling the latest image and restarting the container
&lt;/li&gt;
&lt;li&gt;Keep everything isolated and reproducible, just like the rest of your media stack
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For anyone already using Docker for Plex/Jellyfin, Sonarr, Radarr, or other homelab apps, Recyclarr slots right in with minimal effort.&lt;/p&gt;

&lt;h2 id="configuration-setup"&gt; 🛠️ Docker Compose: config setup &lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Create the config folder
&lt;/h3&gt;

&lt;p&gt;First, create a folder for Recyclarr’s configuration. This is where your &lt;code&gt;recyclarr.yml&lt;/code&gt; file will live:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;recyclarr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Add docker-compose.yml
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;docker-compose.yml&lt;/code&gt; in the same directory with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.9"&lt;/span&gt;

&lt;span class="c1"&gt;# Optional: only needed if Sonarr/Radarr are in a different compose file&lt;/span&gt;
&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;media-server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;recyclarr&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/recyclarr/recyclarr:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;recyclarr&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;media-server&lt;/span&gt;   &lt;span class="c1"&gt;# Optional (see explanation below)&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;TZ=Europe/Amsterdam&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./config:/config&lt;/span&gt;   &lt;span class="c1"&gt;# Holds your recyclarr.yml&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example I’ve chosen to use a custom network. This is the approach I recommend if Radarr and Sonarr run in a different docker-compose.yml, since each Compose file creates its own private network by default. Containers in separate files can’t see each other unless you attach them to a shared external network. &lt;/p&gt;

&lt;p&gt;Now to create that network (if you haven't already):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker network create media-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then attach Sonarr, Radarr, and Recyclarr to it (even across different Compose projects).&lt;/p&gt;

&lt;h3&gt;
  
  
  Start recyclarr
&lt;/h3&gt;

&lt;p&gt;Bring it up with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will start Recyclarr and automatically create the ./config folder on your host (if it doesn’t already exist). Inside that folder, you’ll add your recyclarr.yml configuration file in the next step.&lt;/p&gt;

&lt;h2 id="yaml-config"&gt; 📦 Configuration basics (`recyclarr.yml`) &lt;/h2&gt;

&lt;p&gt;The easiest way to get started with Recyclarr is by using the &lt;strong&gt;prebuilt configuration files&lt;/strong&gt;. These come directly from the Recyclarr project and are aligned with &lt;a href="https://trash-guides.info/" rel="noopener noreferrer"&gt;TRaSH-Guides&lt;/a&gt;. For most setups, these configs “just work” with minimal effort.&lt;/p&gt;

&lt;p&gt;You can find them here:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://recyclarr.dev/wiki/guide-configs/" rel="noopener noreferrer"&gt;Prebuilt Configurations (Recyclarr Docs)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/recyclarr/config-templates" rel="noopener noreferrer"&gt;Config Templates Repository (GitHub)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Example config
&lt;/h3&gt;

&lt;p&gt;Place this file in &lt;code&gt;./recyclarr/recyclarr.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;radarr&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
  &lt;span class="na"&gt;remux-web-1080p&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;base_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://radarr:7878&lt;/span&gt;
    &lt;span class="na"&gt;api_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;YOUR_RADARR_API_KEY&lt;/span&gt;
    &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;radarr-quality-definition-movie&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;radarr-quality-profile-remux-web-1080p&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;radarr-custom-formats-remux-web-1080p&lt;/span&gt;

&lt;span class="na"&gt;sonarr&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;web-1080p-v4&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;base_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://sonarr:8989&lt;/span&gt;
    &lt;span class="na"&gt;api_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;YOUR_SONARR_API_KEY&lt;/span&gt;
    &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sonarr-quality-definition-series&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sonarr-v4-quality-profile-web-1080p&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sonarr-v4-custom-formats-web-1080p&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How the templates work
&lt;/h3&gt;

&lt;p&gt;Each &lt;code&gt;include:&lt;/code&gt; line pulls in a prebuilt block of settings. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;radarr-quality-profile-remux-web-1080p&lt;/code&gt; → creates a quality profile for Remux + WEB 1080p.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;radarr-custom-formats-remux-web-1080p&lt;/code&gt; → applies the correct TRaSH-Guides custom formats and scores.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don’t have to define points or custom formats yourself, Recyclarr fetches the latest rules directly from TRaSH-Guides and applies them to your profiles.&lt;/p&gt;

&lt;h3&gt;
  
  
  API keys
&lt;/h3&gt;

&lt;p&gt;Recyclarr needs API keys to talk to your apps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sonarr → Settings → General → Security → API Key&lt;/li&gt;
&lt;li&gt;Radarr → Settings → General → Security → API Key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Copy the keys and paste them into your config.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsbrlajiaiwsuz0szjtnj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsbrlajiaiwsuz0szjtnj.png" alt="Recyclarr api key.png" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Syncing Recyclarr
&lt;/h3&gt;

&lt;p&gt;Once your recyclarr.yml is in place, run a sync:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;recyclarr recyclarr &lt;span class="nb"&gt;sync&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will connect to Sonarr and Radarr, update the quality profiles, and apply the custom formats from TRaSH-Guides.&lt;br&gt;
Once the container is running with &lt;code&gt;docker compose up -d&lt;/code&gt;, it will sync automatically on a schedule (daily by default), so you don’t need to trigger it manually.&lt;br&gt;
If you prefer one-off runs, add the --rm flag so the container is removed after the sync finishes.&lt;/p&gt;

&lt;p&gt;After the sync, you should now see the new quality profiles and custom formats inside Radarr and Sonarr, automatically created and linked to TRaSH-Guides.&lt;/p&gt;

&lt;p&gt;And that’s it, at this point, Recyclarr is up and running and your profiles are ready for use! 🎉&lt;/p&gt;

&lt;h2 id="optional-customization"&gt; 💾 Optional: Customizing Recyclarr &lt;/h2&gt;

&lt;p&gt;The prebuilt configurations are designed to be simple and opinionated. For example, the Radarr profiles often only include two qualities (e.g., Remux and WEB). That’s perfectly fine if you want to keep things minimal, but in reality many people like to include more options (Blu-ray, HDTV, etc.) or add their own rules.&lt;/p&gt;

&lt;p&gt;You can do this by &lt;strong&gt;extending&lt;/strong&gt; the prebuilt templates with your own settings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: adding qualities and a custom format
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;radarr&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;remux-web-1080p&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;base_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://radarr:7878&lt;/span&gt;
    &lt;span class="na"&gt;api_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;YOUR_RADARR_API_KEY&lt;/span&gt;
    &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;radarr-quality-definition-movie&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;radarr-quality-profile-remux-web-1080p&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;radarr-custom-formats-remux-web-1080p&lt;/span&gt;

    &lt;span class="na"&gt;quality_profiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Remux + WEB 1080p&lt;/span&gt;
        &lt;span class="na"&gt;qualities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Remux-1080p&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;WEB 1080p&lt;/span&gt;
            &lt;span class="na"&gt;qualities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;WEBDL-1080p&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;WEBRip-1080p&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bluray-1080p&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HDTV-1080p&lt;/span&gt;

    &lt;span class="na"&gt;custom_formats&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;trash_ids&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;90a6f9a284dff5103f6346090e6280c8&lt;/span&gt;
        &lt;span class="na"&gt;assign_scores_to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Remux + WEB 1080p&lt;/span&gt;
            &lt;span class="na"&gt;score&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;-100000&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We override the quality profile to add more qualities beyond the two defaults from the template (Blu-ray and HDTV in addition to Remux/WEB).&lt;/li&gt;
&lt;li&gt;We add a custom format for “Low Quality releases” and apply it to the same profile, so Radarr avoids grabbing those.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: If you already have a custom format with the same name in Radarr/Sonarr, Recyclarr will skip updating it. To ensure the new settings are applied, delete the existing custom format first and then run a sync.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The full list of available TRaSH IDs (custom formats you can add) can be found here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://trash-guides.info/Radarr/Radarr-collection-of-custom-formats/" rel="noopener noreferrer"&gt;TRaSH-Guides Custom Formats Radarr&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://trash-guides.info/Sonarr/sonarr-collection-of-custom-formats/" rel="noopener noreferrer"&gt;TRaSH-Guides Custom Formats Sonarr&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;For most users, the defaults from the templates are plenty. But if you want extra control over which qualities are allowed, or you want to filter out certain release types, this kind of customization gives you the flexibility to tailor everything to your preferences.&lt;/p&gt;

&lt;p&gt;Also make sure after every change to run &lt;code&gt;docker-compose exec recyclarr recyclarr sync&lt;/code&gt;&lt;/p&gt;

&lt;h2 id="final-thoughts"&gt; ✅ Final Thoughts &lt;/h2&gt;

&lt;p&gt;Setting up quality profiles and custom formats in Sonarr and Radarr can be tedious, especially if you want to follow all the recommendations from TRaSH-Guides. Recyclarr takes that whole process off your plate. With a single sync, it creates the profiles for you, applies the right custom formats, and keeps everything updated as TRaSH-Guides evolves.  &lt;/p&gt;

&lt;p&gt;Running it in Docker makes the setup even easier, no manual installs, simple updates, and it just runs quietly in the background. Instead of spending time tweaking settings, you get a media server that’s always in line with community best practices, without the extra work.&lt;/p&gt;

&lt;p&gt;Thanks for reading! &lt;/p&gt;

&lt;p&gt;Originally published on my site: &lt;a href="https://zerotohomelab.cloudboxhub.com/recyclarr-docker-easy-trash-guides-sync-for-your-media-server/" rel="noopener noreferrer"&gt;ZeroToHomeLab&lt;/a&gt; I’ll post updates there if anything changes.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>mediaserver</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Set Up Cloudflare Tunnel on Linux for Secure Remote Access to Your Apps</title>
      <dc:creator>Marin</dc:creator>
      <pubDate>Wed, 27 Aug 2025 14:43:38 +0000</pubDate>
      <link>https://dev.to/marin84719/set-up-cloudflare-tunnel-on-linux-for-secure-remote-access-to-your-apps-54fc</link>
      <guid>https://dev.to/marin84719/set-up-cloudflare-tunnel-on-linux-for-secure-remote-access-to-your-apps-54fc</guid>
      <description>&lt;p&gt;You want to access your self-hosted apps or services from anywhere, but you don’t want to mess with port forwarding, deal with firewalls, or expose your home network to the internet.&lt;br&gt;
You just want a simple, secure way to make your local app publicly available, without breaking things or worrying about security.&lt;br&gt;
If that sounds like you, you’re in the right place.&lt;/p&gt;
&lt;h2&gt;
  
  
  📑 Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt; 📘 Introduction &lt;/li&gt;
&lt;li&gt; 🌩️ What Is Cloudflare, and Why Use a Tunnel? &lt;/li&gt;
&lt;li&gt; 🌐 Before We Touch the Server: Get a Domain &amp;amp; Set Up Cloudflare &lt;/li&gt;
&lt;li&gt; 💾 Set Up Cloudflared on Your Linux Server &lt;/li&gt;
&lt;li&gt; 🌍 Connect Your Tunnel to a Subdomain in Cloudflare &lt;/li&gt;
&lt;li&gt; ✅ Final Thoughts &lt;/li&gt;
&lt;/ul&gt;



&lt;h2 id="introduction"&gt; 📘 Introduction &lt;/h2&gt;

&lt;p&gt;Self-hosting is awesome, but getting remote access securely can be one of the most frustrating parts, especially if you’re new to it.&lt;/p&gt;

&lt;p&gt;Maybe you’ve got something like a Ghost blog, a media-server, or a dashboard running on your home server, and now you're thinking:&lt;br&gt;
“It works great locally, but how do I access this when I’m not at home?”&lt;/p&gt;

&lt;p&gt;Sure, you could open ports on your router or try to set up a reverse proxy with SSL certificates, but for a lot of people, that’s just too complicated or risky.&lt;/p&gt;

&lt;p&gt;That’s where Cloudflare Tunnel comes in.&lt;/p&gt;

&lt;p&gt;It’s a free tool that lets you securely expose any service running on your home network to the internet without opening any ports. It handles the routing, encryption (SSL), and DNS for you, and it works even behind CGNAT or dynamic IPs.&lt;/p&gt;

&lt;p&gt;In this guide, I’ll show you how to set up a Cloudflare Tunnel using a self-hosted &lt;a href="https://zerotohomelab.cloudboxhub.com/how-to-self-host-a-ghost-blog-with-docker-compose-on-linux-2025-edition/" rel="noopener noreferrer"&gt;Ghost blog&lt;/a&gt; as the example, but the same steps will work for just about any local app or web service.&lt;/p&gt;



&lt;h2 id="cloudflare"&gt; 🌩️ What Is Cloudflare, and Why Use a Tunnel? &lt;/h2&gt;

&lt;p&gt;Before we get into the setup, let’s take a moment to understand what Cloudflare is, and why their tunnel feature is so useful for home servers and self-hosted apps.&lt;/p&gt;
&lt;h3&gt;
  
  
  What is Cloudflare?
&lt;/h3&gt;

&lt;p&gt;Cloudflare acts as an intelligent middle layer between your server and the public internet. It protects your site from malicious traffic, manages DNS, and improves speed and reliability by optimizing how data is delivered to your visitors.&lt;/p&gt;
&lt;h3&gt;
  
  
  What is a Cloudflare tunnel
&lt;/h3&gt;

&lt;p&gt;Cloudflare Tunnel (previously known as Argo Tunnel or cloudflared) creates a secure, outbound connection from your server to Cloudflare’s network.&lt;/p&gt;

&lt;p&gt;This tunnel allows you to make a service (like a blog, media-server, or app) available on the internet, without exposing your actual network or configuring your router.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why use Cloudflare tunnel
&lt;/h3&gt;

&lt;p&gt;Here’s why Cloudflare Tunnel is a great choice for self-hosted services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No Port Forwarding or Public IP Needed&lt;/strong&gt; - You don’t need to open any ports on your router, set up dynamic DNS, or deal with firewalls. Your server makes an outbound connection to Cloudflare, keeping your home network private and secure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-In HTTPS&lt;/strong&gt; - Cloudflare provides a free SSL certificate for your domain, automatically. Your site will be served over HTTPS without you needing to configure or renew certificates on your server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple Setup&lt;/strong&gt; - You don’t need to be a networking expert. Cloudflare Tunnel can be set up with a few commands.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalable&lt;/strong&gt; - You can route multiple subdomains and apps through the same tunnel config, great for expanding your setup over time.&lt;/li&gt;
&lt;/ul&gt;



&lt;h2 id="cloudflare-account"&gt;🌐 Before We Touch the Server: Get a Domain &amp;amp; Set Up Cloudflare&lt;/h2&gt;

&lt;p&gt;Before we dive into terminal commands and configuration files, let’s get everything ready in Cloudflare. This step is all about setting up your &lt;strong&gt;domain&lt;/strong&gt;, &lt;strong&gt;DNS&lt;/strong&gt;, and &lt;strong&gt;Cloudflare account&lt;/strong&gt;, so your server can later connect securely through the tunnel.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1: Get a Domain Name
&lt;/h3&gt;

&lt;p&gt;To make your self-hosted app accessible from anywhere, you’ll need a domain like &lt;code&gt;cloudboxhub.com&lt;/code&gt;. While you &lt;em&gt;can&lt;/em&gt; use Cloudflare Tunnel without one, a custom domain makes things cleaner — especially when setting up HTTPS.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 I personally recommend &lt;a href="https://www.dynadot.com/?rsc=zerotohomelab&amp;amp;rsctrn=zerotohomelab&amp;amp;rscreg=zerotohomelab&amp;amp;rsceh=zerotohomelab&amp;amp;rscsb=zerotohomelab&amp;amp;rscco=zerotohomelab&amp;amp;rscbo=zerotohomelab" rel="noopener noreferrer"&gt;Dynadot&lt;/a&gt; (This is an affiliate link). It’s beginner-friendly, affordable, and reliable. I use it for this blog, and all screenshots in this guide will be based on Dynadot, which might make things easier to follow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Choose something short, relevant, and easy to remember.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2: Create a Free Cloudflare Account
&lt;/h3&gt;

&lt;p&gt;Go to &lt;a href="https://dash.cloudflare.com/sign-up" rel="noopener noreferrer"&gt;cloudflare.com&lt;/a&gt; and create a free account.&lt;/p&gt;

&lt;p&gt;Once you’re logged in, go to the &lt;strong&gt;Account Home&lt;/strong&gt; tab and click &lt;strong&gt;"Add a Domain"&lt;/strong&gt;. Enter your domain name and follow the prompts to continue.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3: Change Your Nameservers
&lt;/h3&gt;

&lt;p&gt;On the next step, select &lt;strong&gt;"Quick Scan DNS Records."&lt;/strong&gt; Cloudflare will scan your current DNS settings and display what it found. You can skip editing for now, just click &lt;strong&gt;Continue&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Next, Cloudflare will show you &lt;strong&gt;two nameservers&lt;/strong&gt; to use.&lt;/p&gt;

&lt;p&gt;Now head back to your domain registrar (in this case, Dynadot), check the box next to your domain, click the &lt;strong&gt;“Action”&lt;/strong&gt; button, and choose &lt;strong&gt;“DNS Settings.”&lt;/strong&gt; Then enter the Cloudflare-provided nameservers like in the screenshot below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb2ugvbzh80wt2pvifpit.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb2ugvbzh80wt2pvifpit.png" alt="Screenshot 2025-07-20 201423.png" width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⏳ It may take a few minutes (up to 24 hours) for DNS changes to fully propagate. Cloudflare will notify you once your domain is connected and active.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now that your Cloudflare account is set up and your domain is connected, we can move over to the server.&lt;/p&gt;

&lt;p&gt;We’ll create the subdomain and DNS record &lt;strong&gt;after the tunnel is created&lt;/strong&gt;, since we’ll need the unique tunnel address from Cloudflare at that point.&lt;/p&gt;



&lt;h2 id="install-cloudflared"&gt;💾 Set Up Cloudflared on Your Linux Server&lt;/h2&gt;

&lt;p&gt;Now that your domain is connected to Cloudflare, it’s time to set up the tunnel from your linux server. We’ll use a small tool called &lt;code&gt;cloudflared&lt;/code&gt; to create a secure, outbound connection to Cloudflare’s network.&lt;/p&gt;

&lt;p&gt;This allows you to expose local apps without opening any ports or touching your router.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1: Install Cloudflared
&lt;/h3&gt;

&lt;p&gt;On Debian or Ubuntu, you can install Cloudflared with the official package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb &lt;span class="nt"&gt;-o&lt;/span&gt; cloudflared.deb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dpkg &lt;span class="nt"&gt;-i&lt;/span&gt; cloudflared.deb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Authenticate Cloudflared
&lt;/h3&gt;

&lt;p&gt;Run this command to connect your server to your Cloudflare account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cloudflared tunnel login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will open a browser window. Log into your Cloudflare account and select the domain you added earlier (e.g. cloudboxhub.com).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you're using something like PuTTY or a remote terminal, pressing Ctrl+C might cancel the login.&lt;br&gt;
Instead, highlight the full URL to copy it, then use Ctrl+Shift+C to copy safely.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After approving, Cloudflared will store a credentials file in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.cloudflared/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The dot (.) at the start makes this a hidden folder. You won’t see it with &lt;code&gt;ls&lt;/code&gt; unless you run &lt;code&gt;ls -a&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 3: Create a Tunnel
&lt;/h3&gt;

&lt;p&gt;Now create a named tunnel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cloudflared tunnel create myghostblog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A new tunnel ID&lt;/li&gt;
&lt;li&gt;A credentials file linked to this tunnel&lt;/li&gt;
&lt;li&gt;An entry in your Cloudflare dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can name it anything you want — myghostblog, ghost-tunnel, media-tunnel etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Create a Config File
&lt;/h3&gt;

&lt;p&gt;Next, create a config file so Cloudflared knows what to connect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/cloudflared/config.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste in the following (adjust as needed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tunnel: myghostblog
credentials-file: /home/yourusername/.cloudflared/&amp;lt;your-tunnel-ID&amp;gt;.json

ingress:
  - &lt;span class="nb"&gt;hostname&lt;/span&gt;: blog.cloudboxhub.xyz
    service: http://localhost:2368
  - service: http_status:404
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🔁 Replace:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;yourusername&lt;/code&gt; with your Linux username&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;your-tunnel-ID&amp;gt;&lt;/code&gt; with the actual filename inside ~/.cloudflared/&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;blog.cloudboxhub.xyz&lt;/code&gt; with your actual subdomain&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;2368&lt;/code&gt; with the port used by your Docker container or app&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 5: Run the Tunnel as a Service
&lt;/h3&gt;

&lt;p&gt;To make the tunnel start automatically on boot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;cloudflared service &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then start the tunnel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;cloudflared
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start cloudflared
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl status cloudflared
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should show that the cloudflared service up and running:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fptebcilr736e8r4z6sqt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fptebcilr736e8r4z6sqt.png" alt="Screenshot 2025-07-23 160349.png" width="800" height="165"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If everything is set up properly, your tunnel is now running in the background!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Alternative: You can also run the tunnel manually using:&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;cloudflared tunnel run myghostblog
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;However, this only works while your terminal session is open. For long-term or  production use, I strongly recommend using the systemd service instead.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2 id="cloudflare-dns"&gt;🌍 Connect Your Tunnel to a Subdomain in Cloudflare&lt;/h2&gt;

&lt;p&gt;Now that your tunnel is up and running on your server, let’s make it accessible from the internet by pointing your domain (or subdomain) to it.&lt;/p&gt;

&lt;p&gt;We’ll do this by creating a &lt;strong&gt;CNAME DNS record&lt;/strong&gt; in your Cloudflare dashboard that maps your chosen subdomain (like &lt;code&gt;blog.cloudboxhub.xyz&lt;/code&gt;) to the Cloudflare tunnel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Get Your Tunnel’s Public Address
&lt;/h3&gt;

&lt;p&gt;Run this command to get your tunnel’s unique hostname:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cloudflared tunnel list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see output like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frtc6t5klccxu1vs9lbp2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frtc6t5klccxu1vs9lbp2.png" alt="Screenshot 2025-07-23 165045.png" width="800" height="61"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The ID you see is your Tunnel ID (don’t confuse it with the Connector ID). To get your public tunnel address, just append .cfargotunnel.com to that ID:&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;code&gt;80f75045-1c5f-42e0-a665-15711669e934.cfargotunnel.com&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is your tunnel’s public Cloudflare endpoint, it's how Cloudflare routes traffic to your linux server through the tunnel you created. This will be the target for your DNS record in the next step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Create a CNAME Record in Cloudflare
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to your &lt;a href="https://dash.cloudflare.com/" rel="noopener noreferrer"&gt;Cloudflare dashboard&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Select your domain (e.g. cloudboxhub.xyz)&lt;/li&gt;
&lt;li&gt;Navigate to the &lt;strong&gt;DNS&lt;/strong&gt; tab → &lt;strong&gt;Records&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;“Add Record”&lt;/strong&gt; and fill in:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Type&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CNAME&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Name&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;blog&lt;/code&gt; &lt;em&gt;(or whatever subdomain you want)&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Target&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;your-tunnel-id.cfargotunnel.com&lt;/code&gt; &lt;em&gt;(from step 1)&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Proxy status&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;☁️ Proxied (enabled)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TTL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Auto&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Then click Save.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Enabling “Proxied” ensures Cloudflare handles traffic securely, including HTTPS.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs0az4wlukyic0yk0j3kn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs0az4wlukyic0yk0j3kn.png" alt="Screenshot 2025-07-23 223742.png" width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Done!
&lt;/h3&gt;

&lt;p&gt;Your subdomain (e.g. blog.cloudboxhub.xyz) now points to your tunnel, and Cloudflare is handling the traffic securely and without exposing your local network.&lt;/p&gt;

&lt;p&gt;You can now visit your domain and access your self-hosted app from anywhere!&lt;/p&gt;

&lt;h3&gt;
  
  
  Verify It’s Working
&lt;/h3&gt;

&lt;p&gt;Open your domain in any browser.&lt;/p&gt;

&lt;p&gt;If everything is set up correctly, your self-hosted app should load right away, securely, and without any port forwarding.&lt;/p&gt;

&lt;p&gt;If it doesn’t load:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Double-check that your subdomain in Cloudflare matches the one in your tunnel config file&lt;/li&gt;
&lt;li&gt;Make sure the app you’re tunneling (like Ghost) is running and listening on the correct internal port&lt;/li&gt;
&lt;/ul&gt;




&lt;h2 id="final-thoughts"&gt;✅ Final Thoughts&lt;/h2&gt;

&lt;p&gt;Congrats! You just set up a secure, public-facing tunnel from your home server to the internet using Cloudflare Tunnel!&lt;/p&gt;

&lt;p&gt;You did it &lt;strong&gt;without opening a single port&lt;/strong&gt;, messing with firewalls, or worrying about dynamic IPs. That’s a huge win for privacy, security, and simplicity — especially for home lab users.&lt;/p&gt;

&lt;p&gt;Whether you’re self-hosting Ghost, a dashboard, or any other app, this tunnel is your gateway to making it accessible from anywhere, the safe way.&lt;/p&gt;

&lt;p&gt;Originally published on my site: &lt;a href="https://zerotohomelab.cloudboxhub.com/set-up-cloudflare-tunnel-on-linux-for-secure-remote-access-to-your-apps/" rel="noopener noreferrer"&gt;ZeroToHomeLab&lt;/a&gt; I’ll post updates there if anything changes.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>tutorial</category>
      <category>homeserver</category>
      <category>networking</category>
    </item>
    <item>
      <title>How to Self-Host a Ghost Blog with Docker Compose on Linux</title>
      <dc:creator>Marin</dc:creator>
      <pubDate>Tue, 29 Jul 2025 16:03:53 +0000</pubDate>
      <link>https://dev.to/marin84719/how-to-self-host-a-ghost-blog-with-docker-compose-on-linux-4l3d</link>
      <guid>https://dev.to/marin84719/how-to-self-host-a-ghost-blog-with-docker-compose-on-linux-4l3d</guid>
      <description>&lt;p&gt;So, you’ve got a Linux server running at home, and now you're thinking, "Why not use it to share my thoughts with the world?"&lt;br&gt;
Maybe you want to write tutorials, document your projects, or just start a personal blog using something clean and modern like Ghost, which is exactly what I’m doing. If that sounds like you, then you’re in the right place!&lt;/p&gt;

&lt;h2&gt; 📑 Table of Contents &lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt; 📘 Introduction&lt;/li&gt;
&lt;li&gt; 🛠️ Prerequisites &lt;/li&gt;
&lt;li&gt; 📧 Why You Should Configure Email (SMTP) &lt;/li&gt;
&lt;li&gt; 📝 Writing the 'docker-compose.yml' File &lt;/li&gt;
&lt;li&gt; ▶️ Starting and Accessing the Blog &lt;/li&gt;
&lt;li&gt; 🧰 Managing Your Ghost Blog with Docker &lt;/li&gt;
&lt;li&gt; 🌍 Want to Use a Domain and HTTPS? &lt;/li&gt;
&lt;li&gt; 🐞 Troubleshooting &lt;/li&gt;
&lt;li&gt; ✅ Final Thoughts &lt;/li&gt;
&lt;/ul&gt;



&lt;h2 id="introduction"&gt;📘 Introduction &lt;/h2&gt;

&lt;p&gt;In this guide, I’ll walk you through how to self-host a Ghost blog using Docker Compose. While it’s beginner-friendly in the sense that it’s step-by-step and avoids unnecessary complexity, I will assume you’re somewhat familiar with using the terminal and Docker. This helps keep the guide focused, efficient, and practical, so we won’t dive too deeply into explaining every tool or command.&lt;/p&gt;

&lt;p&gt;By the end, you’ll have your own Ghost blog up and running, ready to publish posts and fully under your control.&lt;/p&gt;



&lt;h2 id="prerequisites"&gt; 🛠️ Prerequisites &lt;/h2&gt;

&lt;p&gt;Before we get started, make sure you have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ A &lt;strong&gt;Linux server&lt;/strong&gt; (can be a home server, Raspberry Pi, or old PC running Ubuntu/Debian)&lt;/li&gt;
&lt;li&gt;✅ Basic familiarity with the &lt;strong&gt;Linux terminal&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;✅ &lt;a href="https://docs.docker.com/engine/install/ubuntu/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; and &lt;a href="https://docs.docker.com/compose/install/linux/" rel="noopener noreferrer"&gt;Docker Compose&lt;/a&gt; already installed
&lt;/li&gt;
&lt;li&gt;✅ A working &lt;strong&gt;SMTP email account&lt;/strong&gt; (e.g. Gmail) for sending login and system emails
&amp;gt; Ghost requires working email even for local login, it sends a verification link to complete the sign-in process. If email isn’t set up, you may get stuck in a login loop. I’ll explain more below.&lt;/li&gt;
&lt;li&gt;✅ (Optional but recommended) A &lt;strong&gt;domain name&lt;/strong&gt; for public access&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Do I Need a Domain Name?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You don’t need a domain to get started. You can run Ghost locally on your home network using &lt;code&gt;localhost&lt;/code&gt; or your local IP.&lt;/p&gt;

&lt;p&gt;That said, using a custom domain with a service like &lt;strong&gt;Cloudflare Tunnel&lt;/strong&gt; makes accessing your blog from anywhere much easier and more secure.&lt;/p&gt;

&lt;p&gt;I cover how to set that up in &lt;a href="https://zerotohomelab.cloudboxhub.com/set-up-cloudflare-tunnel-on-linux-for-secure-remote-access-to-your-apps/" rel="noopener noreferrer"&gt;this guide on setting up a Cloudflare Tunnel on Linux&lt;/a&gt;. You can also start without a domain and add one later.&lt;/p&gt;
&lt;/blockquote&gt;



&lt;h2 id="SMTP-email"&gt; 📧 Why You Should Configure Email (SMTP)&lt;/h2&gt;

&lt;p&gt;When you first set up Ghost, it asks you to create an admin account. Normally, this triggers a confirmation email, but if email isn’t configured, it silently fails, and you might still get through the setup.&lt;/p&gt;

&lt;p&gt;However, &lt;strong&gt;Ghost requires working email to log in later.&lt;/strong&gt; If SMTP isn't properly set up, the login screen will try to send you a verification email, fail silently, and get stuck in a loop before eventually timing out.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 That’s why I recommend setting up SMTP (email) right from the beginning, even if you’re just running Ghost locally.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can use Gmail’s SMTP service with an &lt;a href="https://support.google.com/accounts/answer/185833" rel="noopener noreferrer"&gt;App Password&lt;/a&gt;, or any other email provider that supports SMTP. We'll include this in the Docker Compose file below.&lt;/p&gt;



&lt;h2 id="setup"&gt; 📝 Writing the 'docker-compose.yml' File &lt;/h2&gt;

&lt;p&gt;Before we launch your Ghost blog, let’s create a dedicated directory to keep things organized, and write your Docker Compose configuration.&lt;/p&gt;


&lt;h3&gt;
  
  
  Step 1: Create Your Project Folder
&lt;/h3&gt;

&lt;p&gt;First, create a folder where all your Ghost files and configuration will live:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p ~/ghost-blog
cd ~/ghost-blog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Create the Compose File
&lt;/h3&gt;

&lt;p&gt;Now let’s create your docker-compose.yml file inside the folder.&lt;/p&gt;

&lt;p&gt;I'll be using nano, but you can use vim or any other terminal-based text editor you prefer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nano docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside this file, paste the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ghost&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghost:5&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghost_blog&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2368:2368"&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://192.168.1.1:2368&lt;/span&gt;
      &lt;span class="na"&gt;mail__transport&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SMTP&lt;/span&gt;
      &lt;span class="na"&gt;mail__options__host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;smtp.gmail.com&lt;/span&gt;
      &lt;span class="na"&gt;mail__options__port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;587&lt;/span&gt;
      &lt;span class="na"&gt;mail__options__secure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;
      &lt;span class="na"&gt;mail__options__auth__user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-user@gmail.com&lt;/span&gt;
      &lt;span class="na"&gt;mail__options__auth__pass&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-app-password&lt;/span&gt;
      &lt;span class="na"&gt;database__client&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql&lt;/span&gt;
      &lt;span class="na"&gt;database__connection__host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
      &lt;span class="na"&gt;database__connection__user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghostuser&lt;/span&gt;
      &lt;span class="na"&gt;database__connection__password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;password&lt;/span&gt;
      &lt;span class="na"&gt;database__connection__database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghostdb&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./content:/var/lib/ghost/content&lt;/span&gt;

  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql:8&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghost_mysql&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-root-password&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghostdb&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghostuser&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;password&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db_data:/var/lib/mysql&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To get this working on your own server, you only need to update a few values in the &lt;code&gt;docker-compose.yml&lt;/code&gt; file:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;🔑 Key&lt;/th&gt;
&lt;th&gt;📝 What It Does&lt;/th&gt;
&lt;th&gt;✅ What You Should Do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;url:&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The public address of your blog&lt;/td&gt;
&lt;td&gt;Replace &lt;code&gt;http://192.168.1.1:2368&lt;/code&gt; with your &lt;strong&gt;server’s IP address&lt;/strong&gt; (e.g. &lt;code&gt;http://192.168.0.101:2368&lt;/code&gt;) or your &lt;strong&gt;custom domain&lt;/strong&gt; later (e.g. &lt;code&gt;https://zerotohomelab.cloudboxhub.com&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mail__options__auth__user:&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The email Ghost will send from&lt;/td&gt;
&lt;td&gt;Use your actual &lt;strong&gt;Gmail address&lt;/strong&gt; (e.g. &lt;code&gt;you@gmail.com&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mail__options__auth__pass:&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The SMTP password for Gmail&lt;/td&gt;
&lt;td&gt;Use a &lt;strong&gt;Gmail App Password&lt;/strong&gt;, not your normal password. 👉 &lt;a href="https://myaccount.google.com/apppasswords" rel="noopener noreferrer"&gt;Generate one here&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MYSQL_ROOT_PASSWORD:&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The MySQL admin password&lt;/td&gt;
&lt;td&gt;Set a strong password. It’s required but not used by Ghost directly.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;MYSQL_USER&lt;/code&gt;, &lt;code&gt;MYSQL_PASSWORD&lt;/code&gt;, &lt;code&gt;MYSQL_DATABASE&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;MySQL credentials Ghost will use&lt;/td&gt;
&lt;td&gt;You can change these if you like—just make sure the values match in both the &lt;code&gt;ghost&lt;/code&gt; and &lt;code&gt;db&lt;/code&gt; sections.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Once you've made those changes, you're ready to start your blog!&lt;/p&gt;




&lt;h2 id="start-and-access"&gt; ▶️ Starting and Accessing the Blog &lt;/h2&gt;

&lt;p&gt;Now that your &lt;code&gt;docker-compose.yml&lt;/code&gt; file is ready and properly configured, it's time to bring your Ghost blog to life.&lt;/p&gt;




&lt;h3&gt;
  
  
  Start the Containers
&lt;/h3&gt;

&lt;p&gt;In the same folder where your file is saved (&lt;code&gt;~/ghost-blog&lt;/code&gt;), run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Check That Everything is Running
&lt;/h3&gt;

&lt;p&gt;To verify everything is running as expected, check the container status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see both the ghost_blog and ghost_mysql containers listed and marked as "Up".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmyycde3iscziv529kvei.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmyycde3iscziv529kvei.png" alt="screenshot_docker_containers.png" width="800" height="39"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Access the Blog in Your Browser
&lt;/h3&gt;

&lt;p&gt;Now, go to your browser and visit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://&amp;lt;your-server-ip&amp;gt;:2368/ghost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;&amp;lt;your-server-ip&amp;gt;&lt;/code&gt; with the address you set in the url: field of your docker-compose.yml (e.g. 192.168.0.x).&lt;/p&gt;

&lt;p&gt;If all is working, you’ll see the Ghost welcome screen with a prompt to set up your admin account, like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fads7sueptmc6o28cxffs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fads7sueptmc6o28cxffs.png" alt="admin_setup_ghost.png" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2 id="manage-your-blog"&gt; 🧰 Managing Your Ghost Blog with Docker &lt;/h2&gt;

&lt;p&gt;If you’ve used Docker before, managing Ghost will feel familiar. Here are just a few Ghost-specific things to keep in mind.&lt;/p&gt;

&lt;h3&gt;
  
  
  Updating Ghost
&lt;/h3&gt;

&lt;p&gt;To upgrade to the latest Ghost version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose pull
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This downloads the latest Ghost image and restarts the blog using your current configuration. Your content and database are safe as long as your volumes remain intact.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backing Up Your Blog
&lt;/h3&gt;

&lt;p&gt;Ghost stores your data in two places:&lt;/p&gt;

&lt;p&gt;./content/ — Contains file-based data such as images, themes, and logs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MySQL volume&lt;/strong&gt; — Stores the database, including posts, users, and settings.&lt;/p&gt;

&lt;p&gt;To back the content folder up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; ./content ~/ghost-backup/content
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To backup the MySQL database run the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec &lt;/span&gt;ghost_mysql mysqldump &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="s1"&gt;'ghostdbpass'&lt;/span&gt; ghostdb &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/ghost-backup/ghostdb.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Replace &lt;code&gt;ghostdb&lt;/code&gt; with your actual database name and replace &lt;code&gt;ghostdbpass&lt;/code&gt; with your actual MySQL root password. If you're passing the password inline, make sure there is &lt;strong&gt;no space&lt;/strong&gt; after - p, and enclose the password in quotes:&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="s1"&gt;'yourpassword'&lt;/span&gt;   ✅
&lt;span class="nt"&gt;-p&lt;/span&gt; yourpassword  ❌ &lt;span class="o"&gt;(&lt;/span&gt;won’t work&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;This command connects to the MySQL container and creates a full SQL snapshot of your Ghost database. You can use this file to restore your blog’s data later if something goes wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Restarting / Stopping
&lt;/h3&gt;

&lt;p&gt;For reference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose restart      &lt;span class="c"&gt;# Restart blog&lt;/span&gt;
docker compose down         &lt;span class="c"&gt;# Stop containers&lt;/span&gt;
docker compose down &lt;span class="nt"&gt;-v&lt;/span&gt;      &lt;span class="c"&gt;# Stop and remove all data (be careful!)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Restoring Your Ghost Backup
&lt;/h3&gt;

&lt;p&gt;If something goes wrong or you want to migrate your blog to a new machine, you can restore it using the backup files we created.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Restore the content folder&lt;/strong&gt; (images, themes, etc.):
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; ~/ghost-backup/content ./content
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Restore the database from your .sql file:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;cp&lt;/span&gt; ~/ghost-backup/ghostdb.sql ghost_mysql:/ghostdb.sql
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; ghost_mysql mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="s1"&gt;'ghostdbpass'&lt;/span&gt; ghostdb &amp;lt; /ghostdb.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once restored, restart Ghost:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker compose restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your blog should now be back exactly as it was.&lt;/p&gt;




&lt;h2 id="domain"&gt; 🌍 Want to Use a Domain and HTTPS? &lt;/h2&gt;

&lt;p&gt;In this tutorial, we focused on getting Ghost running locally on your server.&lt;/p&gt;

&lt;p&gt;If you'd like to make your blog publicly accessible using a domain name and secure HTTPS (via Cloudflare Tunnel), check out the next part:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://zerotohomelab.cloudboxhub.com/set-up-cloudflare-tunnel-on-linux-for-secure-remote-access-to-your-apps/" rel="noopener noreferrer"&gt;How to Secure Your Ghost Blog with a Domain and Cloudflare Tunnel&lt;/a&gt;&lt;/p&gt;




&lt;h2 id="troubleshooting"&gt; 🐞 Troubleshooting &lt;/h2&gt;

&lt;p&gt;If you haven't ran into any issues, great, then you can skip this section! Ran into a problem? Here are a few common issues you might face and how to resolve them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can't Log In? SMTP Not Working
&lt;/h3&gt;

&lt;p&gt;Ghost requires a working SMTP setup to send confirmation or login emails. If your SMTP settings are missing or incorrect, you'll get stuck on the login screen, it silently fails to send the verification email, then times out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Double-check the email and app password in your &lt;code&gt;docker-compose.yml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Make sure your SMTP port is correct (587 for Gmail)&lt;/li&gt;
&lt;li&gt;Restart Ghost after editing:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Still stuck? Check the logs (see below) for SMTP-related errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Viewing Ghost Logs
&lt;/h3&gt;

&lt;p&gt;To troubleshoot errors like email issues, startup failures, or database connection problems, use this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose logs ghost_blog &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will stream the Ghost container logs in real time so you can see what's happening under the hood.&lt;/p&gt;

&lt;p&gt;You can also check the MySQL container logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose logs db &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SQLite Is No Longer Supported
&lt;/h3&gt;

&lt;p&gt;Ghost 5.x and later no longer support SQLite as a database engine. If you're coming from older guides or trying to set it up quickly without MySQL, you may see unexplained startup failures, like the following logs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiocncsjuhmj8p67j3p58.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiocncsjuhmj8p67j3p58.png" alt="unknown database.png" width="800" height="257"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Solution:&lt;br&gt;
This guide uses MySQL 8 via Docker, which is the officially supported setup. Make sure you don't try to switch to SQLite manually, it won’t work with recent versions of Ghost.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tip: You can always restart from scratch with:&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose down &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;But be careful, -v also removes all volumes and stored data.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2 id="final-thoughts"&gt; ✅ Final Thoughts &lt;/h2&gt;

&lt;p&gt;If you've followed this guide, you now have a fully self-hosted Ghost blog running on your Linux server with Docker Compose.&lt;/p&gt;

&lt;p&gt;You've taken full control of your content, your data, and your setup — with no need to rely on third-party platforms or expensive hosting services.&lt;/p&gt;

&lt;p&gt;From here, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://zerotohomelab.cloudboxhub.com/set-up-cloudflare-tunnel-on-linux-for-secure-remote-access-to-your-apps/" rel="noopener noreferrer"&gt;Secure your blog with a cloudflare tunnel and a custom domain&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Customize your theme and navigation&lt;/li&gt;
&lt;li&gt;Start writing and publishing right away&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hopefully this has helped you get started with ghost.&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

&lt;p&gt;Originally published on my site: &lt;a href="https://zerotohomelab.cloudboxhub.com/how-to-self-host-a-ghost-blog-with-docker-compose-on-linux-2025-edition/" rel="noopener noreferrer"&gt;ZeroToHomeLab&lt;/a&gt; I’ll post updates there if anything changes.&lt;/p&gt;

</description>
      <category>homeserver</category>
      <category>selfhosting</category>
      <category>linux</category>
      <category>docker</category>
    </item>
  </channel>
</rss>
