Running Waline with Docker on Synology NAS: Complete Setup & Ghost Integration Guide (No More Headaches)

Learn how to set up Waline with Docker on Synology NAS, configure reverse proxy, optional phpMyAdmin, and embed it into your Ghost blog posts. Complete guide with all files, folder structure, and code examples.

Running Waline with Docker on Synology NAS: Complete Setup & Ghost Integration Guide (No More Headaches)

✨ Update: Additional Notes on OAuth and Waline

During my testing, I explored Waline’s built-in OAuth options. In practice, however, they are of limited value for most English-speaking users. QQ and Weibo are not commonly used outside of specific regions, Google login is not supported, Twitter requires a paid plan, and Facebook has tightened its policies so that only business accounts can realistically integrate.

In short, the OAuth menu feels more ornamental than functional. As a result, leaving a comment essentially requires creating an account. Yet most casual visitors are unlikely to provide personal details just to leave a quick remark.

If you do choose Waline, I recommend being aware of this limitation. In fact, it may be more user-friendly to simply hide the OAuth icons altogether—clicking them will not lead to a working login flow anyway.

For my own needs, I will move on and look for a comment system that supports Google OAuth. That said, Waline may still suit those who appreciate its lightweight design, or who find that it aligns neatly with their own requirements. If you fall into that group, feel free to install and configure it as described.


Introduction to Waline and Why Use It

Waline is a lightweight, self-hosted comment system that’s compatible with blogs, documentation, and static sites. Unlike Disqus or other SaaS-based comment systems, Waline gives you data ownership and privacy, while offering a modern commenting experience with reactions, Markdown support, and more.

Running Waline on a Synology NAS with Docker ensures that your comment system is always online, secure, and under your control. And in this guide, I’ll show you how to integrate Waline into your Ghost blog step by step.


Prerequisites for Running Waline on Synology NAS

Before starting, make sure you have:

  • ✅ Synology NAS with Docker installed
  • Docker Compose available
  • ✅ A domain/subdomain (e.g., waline.yourdomain.com)
  • ✅ Ghost blog already running (self-hosted or managed)

Required Folder Structure

Inside your Synology NAS (in the Docker shared folder or your project directory), create the following structure:

waline/
 ├── docker-compose.yaml
 ├── mariadb/                # MariaDB persistent storage
 ├── initdb/                 # Place waline.sql here
 │    └── waline.sql
 ├── logs/                   # Waline app logs
 ├── nginx/
 │    └── nginx.conf
 └── pma/ (optional, for phpMyAdmin)
      └── config.user.inc.php
⚠️ Only create pma/ folder if you plan to use phpMyAdmin.

Preparing Database for Waline

Downloading the Latest waline.sql from Repository

​Always download the latest schema from Waline’s official repository:

👉 Waline GitHub SQL File

Download and save it as:

waline/initdb/waline.sql

Creating docker-compose.yaml for Waline

Here’s the cleaned & secure version of your docker-compose.yaml:

services:
  mariadb:
    image: mariadb:11.4
    container_name: waline-db
    command: >-
      --transaction-isolation=READ-COMMITTED --binlog_format=ROW
      --innodb_file_per_table=1 --character-set-server=utf8mb4
      --collation-server=utf8mb4_unicode_ci
    environment:
      MYSQL_ROOT_PASSWORD: "********"   # CHANGE THIS
      MYSQL_DATABASE: "waline"
      MYSQL_USER: "waline"
      MYSQL_PASSWORD: "********"        # CHANGE THIS
      TZ: "Asia/Seoul"
    volumes:
      - ./mariadb:/var/lib/mysql
      - ./initdb:/docker-entrypoint-initdb.d:ro
    restart: unless-stopped

  waline:
    image: lizheming/waline:latest
    container_name: waline-app
    depends_on:
      mariadb:
        condition: service_healthy
    restart: unless-stopped
    environment:
      TZ: "Asia/Seoul"
      DISABLE_GEO: "true"
      MYSQL_HOST: "mariadb"
      MYSQL_PORT: "3306"
      MYSQL_DB: "waline"
      MYSQL_USER: "waline"
      MYSQL_PASSWORD: "********"          # SAME AS ABOVE
      JWT_TOKEN: "**********************" # Generate a random string
      SITE_NAME: "Your Blog Name"
      SITE_URL: "https://waline.yourdomain.com"
      SECURE_DOMAINS: "yourdomain.com" # For Multiple, Seperate Comma,
      AUTHOR_EMAIL: "your@email.com"
    volumes:
      - ./logs:/app/logs
    expose:
      - "8360"

  nginx:
    image: nginx:alpine
    container_name: waline-nginx
    depends_on:
      waline:
        condition: service_healthy
    restart: unless-stopped
    ports:
      - "18801:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro

  # Optional: phpMyAdmin
  phpmyadmin:
    image: phpmyadmin:latest
    container_name: waline-pma
    restart: unless-stopped
    depends_on:
      mariadb:
        condition: service_healthy
    environment:
      - PMA_ABSOLUTE_URI=https://waline.yourdomain.com/pma/
      - UPLOAD_LIMIT=512M
    volumes:
      - pma_sessions:/sessions
      - ./pma/config.user.inc.php:/etc/phpmyadmin/config.user.inc.php:ro

volumes:
  pma_sessions:

⚠️ Notes:

  • Replace all ******** with your own secure passwords and tokens.
  • If you don’t need phpMyAdmin, remove the phpmyadmin service and related volume.

Nginx Container Config (nginx.conf)

What we keep minimal: DSM handles TLS and most proxy headers. Nginx here is a lightweight HTTP hop inside the stack. We’ll preserve Host for correctness and skip unnecessary overrides.
# /etc/nginx/conf.d/default.conf
server {
  listen 80;
  server_name _;  # DSM forwards the correct Host header

  # Simple health endpoint
  location = /health {
    add_header Content-Type text/plain;
    return 200 'ok';
  }

  # ---------- OPTIONAL: phpMyAdmin ----------
  # Remove this block if phpMyAdmin is not used.
  location = /pma { return 301 https://waline.yourdomain.com/pma/; }  # CHANGE domain
  location ^~ /pma/ {
    proxy_pass http://waline-pma:80/;              # keep trailing slash
    proxy_set_header X-Forwarded-Prefix /pma;      # so app knows it runs under /pma
    proxy_redirect off;
  }

  # ---------- Waline ----------
  location / {
    proxy_pass         http://waline-app:8360;
    proxy_http_version 1.1;

    # Keep Host so the upstream sees the real hostname (safer for absolute URLs/cookies)
    proxy_set_header   Host $host;

    # Reasonable defaults
    proxy_buffering    on;
    proxy_read_timeout 60s;
    proxy_send_timeout 60s;
  }
}

DSM Reverse Proxy Setup

Go to Control Panel → Login Portal → Advanced → Reverse Proxy

  • Source:Protocol: HTTPSHostname: waline.yourdomain.com (CHANGE)Port: 443
  • Destination:Protocol: HTTPHostname: localhostPort: 18801 (our Nginx container above)
  • Apply Custom Headers for forwarding.
Header NameValuePurpose
Host$hostPreserve original host
X-Real-IP$remote_addrClient IP
X-Forwarded-For$proxy_add_x_forwarded_forForward chain
X-Forwarded-ProtohttpsCorrect scheme
X-Forwarded-Host$hostPreserve
X-Forwarded-Port443Match HTTPS

Configuring phpMyAdmin (Optional)

If you enabled phpMyAdmin, edit config.user.inc.php:

<?php
$cfg['blowfish_secret'] = '********'; // At least 32 chars

$i = 0;
$i++;
$cfg['Servers'][$i]['host'] = 'mariadb';
$cfg['Servers'][$i]['port'] = '3306';
$cfg['Servers'][$i]['user'] = 'root';
$cfg['Servers'][$i]['auth_type'] = 'cookie';

$cfg['LoginCookieRecall'] = true;
$cfg['LoginCookieValidity'] = 86400;
$cfg['LoginCookieStore']    = 0;

$cfg['UploadDir'] = '/var/www/html/tmp';
$cfg['SaveDir']   = '/var/www/html/tmp';

$cfg['DefaultLang'] = 'en';
$cfg['ThemeDefault'] = 'pmahomme';
$cfg['QueryHistoryDB'] = false;
$cfg['QueryHistoryMax'] = 100;

Boot Up & Post-Install Admin Setup

  1. Start the stack in your waline/ directory:docker compose up -d
    (or docker-compose up -d on older setups)
  2. Create the admin account (first-run setup):
    Visit https://waline.yourdomain.com/ui (or /ui/register).
    The very first account you create becomes the administrator.
    After that, manage comments anytime at https://waline.yourdomain.com/ui.
If you’re using SECURE_DOMAINS, ensure your blog’s domain is listed so sign-in and links work correctly. See Waline’s server environment reference for options. Waline's server environment reference

Embedding Waline into Ghost Blog

Embed Waline into Ghost Theme (post.hbs)

We’ll keep it simple and theme-local (no global injection). Insert the script right before </article> and the CSS at the very bottom of post.hbs. Replace waline.yourdomain.com.

Insert before </article>:

{{!– Waline comments --}}
<div id="waline"></div>
<script src="https://unpkg.com/@waline/client@latest/dist/waline.umd.js"></script>
<script>
  (function initWaline(){
    if (!window.Waline) return setTimeout(initWaline, 100);
    Waline.init({
      el: '#waline',
      serverURL: 'https://waline.yourdomain.com',
      path: '{{url absolute="true"}}',
      lang: 'en',
      dark: false,
      reaction: true,
      requiredMeta: ['nick']
    });
  })();
</script>

Add CSS at the bottom of post.hbs:

<link rel="stylesheet" href="https://unpkg.com/@waline/client@latest/dist/waline.css">
<style>
#waline {
  --waline-font-size: 16px;
  max-width: 820px;
  margin: 3rem auto;
  line-height: 1.6;
  color: #222;
}
#waline .wl-comment-content,
#waline .wl-editor textarea {
  font-size: 0.95rem;
}
#waline .wl-btn {
  font-size: 0.9rem;
  padding: 0.4rem 1rem;
  border-radius: 6px;
}
#waline input::placeholder,
#waline textarea::placeholder { color:#999; }
#waline .wl-addr { display: none !important; }
</style>

Zip & Upload Your Theme (theme.modified.v2.zip)

  1. Export your current theme from Ghost Admin → Settings → Theme (for backup).
  2. Apply the edits above in your theme folder.
  3. Zip the theme as theme.modified.v2.zip.
  4. Upload the zip in Ghost Admin → Settings → Theme → Upload theme.
  5. Click Activate and open any post to confirm Waline renders.

FAQs about Running Waline on Synology NAS

Q1: Do I need phpMyAdmin?
👉 No, it’s optional. You can manage Waline without it.

Q2: What if Waline fails to connect to MariaDB?
👉 Check your MYSQL_USER, MYSQL_PASSWORD, and ensure waline.sql initialized correctly.

Q3: Can I use SQLite instead of MariaDB?
👉 Yes, but for Synology + Ghost production use, MariaDB is recommended.

Q4: How do I regenerate JWT_TOKEN?
👉 Use any random generator (openssl rand -base64 32).

Q5: Can I use DSM without custom headers?
👉 Not recommended. Headers ensure correct IP logging and HTTPS trust.

Q6: How do I update Waline?
👉 Run docker-compose pull && docker-compose up -d.


✅ Conclusion

By following this guide, you’ve successfully:

  • Set up Waline with Docker on Synology NAS
  • Configured Nginx & DSM Reverse Proxy
  • (Optionally) added phpMyAdmin
  • Embedded Waline directly into your Ghost posts

Now your Ghost blog has a fast, privacy-friendly, modern comment system.