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.
✨ 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:
Download and save it as:
waline/initdb/waline.sqlCreating 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
phpmyadminservice 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.
✅ DSM Custom Headers (Recommended Table)
| Header Name | Value | Purpose |
|---|---|---|
Host | $host | Preserve original host |
X-Real-IP | $remote_addr | Client IP |
X-Forwarded-For | $proxy_add_x_forwarded_for | Forward chain |
X-Forwarded-Proto | https | Correct scheme |
X-Forwarded-Host | $host | Preserve |
X-Forwarded-Port | 443 | Match 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
- Start the stack in your
waline/directory:docker compose up -d
(ordocker-compose up -don older setups) - Create the admin account (first-run setup):
Visithttps://waline.yourdomain.com/ui(or/ui/register).
The very first account you create becomes the administrator.
After that, manage comments anytime athttps://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 referenceEmbedding 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 ofpost.hbs. Replacewaline.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)
- Export your current theme from Ghost Admin → Settings → Theme (for backup).
- Apply the edits above in your theme folder.
- Zip the theme as
theme.modified.v2.zip. - Upload the zip in Ghost Admin → Settings → Theme → Upload theme.
- 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.