How to setup Ghost in a VPS using Docker, Mailgun and SQLite
Two months ago, I decided to move away from the cloud and self-host all of my services and applications on a single VPS.
You know, just like the cool kids are doing.
In this post, I’ll show you exactly how I set up a Ghost instance from scratch. Here’s what we’ll cover:
- Prerequisites: Everything you need to get started.
- Setting Up Ghost with Docker: How to run a Ghost instance using Docker.
- Configuring Nginx: Forwarding requests from your VPS to your blog domain (e.g., blog.domain.com).
- SSL with Let’s Encrypt: Setting up HTTPS for secure connections.
- Bulk Mail Functionality: Using Mailgun to send bulk emails for your blog.
By the end of this guide, you’ll have a fully functional, self-hosted Ghost blog.
Have questions or want to share your setup experience? Let me know in the comment section below!
Prerequisites: Prepare Your VPS and Environment
Before we dive into setting up Ghost, let’s make sure we have everything we need.
Here’s what you’ll need:
- A VPS (Virtual Private Server)
- DNS Settings
- Docker installed
If you’re already set, feel free to jump ahead to the next section!
Choose a VPS Provider
Choosing a VPS comes down to personal preference, as pricing and features are similar.
You can’t go wrong with DigitalOcean, Linode, or Vultr—they’re all reliable.
For this guide, I chose Linode’s Dedicated CPU base plan:
- RAM: 4GB
- Storage: 80GB
- CPUs: 2
- Network In/Out: 40/4 Gbps
Check out their pricing here: Linode Pricing
Prepare DNS Settings
To make your blog accessible, you’ll need to set up a CNAME record in your DNS provider. Here’s an example configuration for a subdomain like 'blog.yourdomain.com':
Name: blog
Type: CNAME
Content: (your VPS IP address)
TTL: 3600
Set Up Docker
Docker helps isolate your Ghost deployment from other services on the VPS.
Here’s how to install Docker on Ubuntu, but if you have any other Linux distribution, check out the docker documentation.
Step 1: Add Docker’s GPG Key
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
Step 2: Add Docker to Apt Sources
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
Step 3: Install Docker Packages
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Once Docker is set up, you’re ready to deploy Ghost. Let’s dive into the setup!
Setting Up Ghost with Docker
Using Docker to deploy Ghost simplifies the setup process, ensuring easy updates, better isolation, and persistent data storage. Let’s get started!
Step 1: Create a Directory for Ghost Files
We’ll create a dedicated directory to store Ghost content and configuration files.
Run the following commands:
mkdir -p ~/projects/ghost_blog
cd ~/projects/ghost_blog
Step 2: Configure Docker Compose for Ghost
Create a docker-compose.yml
file in the directory to define the Ghost service:
version: '3.1'
services:
ghost:
image: ghost:latest
container_name: blog_ghost
restart: always
ports:
- "2368:2368"
environment:
url: http://blog.mydomain.com
database__client: sqlite3
database__connection__filename: /var/lib/ghost/content/data/ghost.db
volumes:
- /home/username/projects/ghost_blog:/var/lib/ghost/content
Key Points:
- Docker Image: The
ghost:latest
image ensures the most up-to-date version of Ghost. - SQLite Database: The database file is stored at
/var/lib/ghost/content/data/ghost.db
for simplicity. - Volumes: Data persistence is ensured by mapping the host directory
/home/username/projects/ghost_blog
to the container's content directory.
For more details, refer to the Ghost Docker documentation.
Step 3: Start and Verify the Ghost Container
Run the following command to start the Ghost container in the background:
sudo docker compose up -d
Check the container status with:
sudo docker ps
Your output should look something like this:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9ecdf7d08764 ghost:latest "docker-entrypoint.s…" 3 days ago Up 3 days 0.0.0.0:2368->2368/tcp, :::2368->2368/tcp blog_ghost
Configuring Nginx
Once Ghost is running on port 2368, the next step is to expose it through a web server like Nginx and secure it with SSL. Nginx acts as a reverse proxy, forwarding traffic from standard web ports (80/443) to Ghost’s internal port (2368) while enabling HTTPS for secure connections.
Step 1: Install Nginx on Your VPS
If Nginx isn’t already installed, use the following commands to install it:
sudo apt install nginx
Step 2: Create an Nginx Configuration File
We’ll set up Nginx to act as a reverse proxy for Ghost. Create a new configuration file:
sudo vim /etc/nginx/sites-available/ghost_blog
Add the following configuration:
server {
listen 80;
server_name blog.mydomain.com;
location / {
proxy_pass http://localhost:2368;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Key Points:
- Proxy: Nginx forwards all traffic to Ghost running on port 2368 using the proxy_pass directive.
Step 3: Enable and Test the Configuration
Enable the configuration by creating a symbolic link:
sudo ln -s /etc/nginx/sites-available/ghost_blog /etc/nginx/sites-enabled/
Test the configuration for syntax errors:
sudo nginx -t
Reload Nginx to apply changes:
sudo systemctl reload nginx
Step 4: Confirm Nginx is Working
Visit your blog in a browser: http://blog.mydomain.com
. If everything is set up correctly, your Ghost blog should load.
Troubleshooting Tips:
- If Nginx doesn’t reload, run
sudo nginx -t
to check for syntax errors. - Ensure the server block file is linked in
/etc/nginx/sites-enabled/
. - Verify that Ghost is running on port 2368 by checking with
sudo docker ps
.
Setting Up SSL with Let’s Encrypt
SSL encrypts traffic between your blog and visitors, ensuring a secure connection.
Let’s Encrypt offers free SSL certificates, and Certbot simplifies the setup and management process.
Here’s how to configure SSL for your Ghost blog.
Step 1: Install Certbot for SSL Management
Install Certbot and the Nginx plugin to manage SSL certificates:
sudo apt install certbot python3-certbot-nginx
Step 2: Obtain and Configure SSL Certificates
Run Certbot to obtain SSL certificates and configure Nginx automatically:
sudo certbot --nginx -d blog.mydomain.com
Certbot will update your Nginx configuration to include SSL settings. The updated configuration should look like this:
nginxCopy codeserver {
listen 80;
server_name blog.mydomain.com;
# Redirect all HTTP traffic to HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name blog.mydomain.com;
ssl_certificate /etc/letsencrypt/live/blog.mydomain.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/blog.mydomain.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
location / {
proxy_pass http://localhost:2368;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Key Points:
- HTTP-to-HTTPS Redirection: Ensures all traffic is securely encrypted.
- SSL Certificate Paths: Certbot manages certificates in
/etc/letsencrypt/
. - Automatic Renewal: Certbot sets up a cron job to renew certificates automatically.
Step 3: Test and Apply SSL Settings
Test the updated Nginx configuration for syntax errors:
sudo nginx -t
If the test passes, reload Nginx to apply the changes:
sudo systemctl reload nginx
Step 4: Update Ghost Configuration to HTTPS
Ensure Ghost uses HTTPS by updating the url
in the docker-compose.yml
file:
environment:
url: https://blog.mydomain.com
Restart the Ghost service to apply the changes:
sudo docker compose down
sudo docker compose up -d
Final Confirmation
Visit your blog at https://blog.mydomain.com
to confirm SSL is active.
You should see the secure lock icon in the browser’s address bar.
Congratulations—your blog is now secured with HTTPS!
Configure Mailgun
The last item on our Ghost installation is to set up the bulk mail functionality. This is essential for sending newsletters and handling bulk email campaigns.
We are using Mailgun since Ghost has a nice easy-to-setup built in newsletter delivery feature around it.
Step 1: Create and Verify a Mailgun Account
- Create a Mailgun account and verify your email.
- Go to Domain Settings under Sending in the Mailgun admin portal.
- Add a subdomain for your blog (e.g.,
mg.mydomain.com
). Using a subdomain protects your domain reputation, isolates email types, and simplifies DNS management.
Step 2: Add and Verify DNS Records
After adding your subdomain, Mailgun will display DNS records (e.g., TXT, CNAME, MX). Add these records to your domain registrar's DNS settings and wait for verification.
Why DNS Verification Matters: Authenticating your domain ensures emails are not flagged as spam and boosts deliverability.
Step 3: Create SMTP Credentials
Once your DNS records are verified:
- Go to the SMTP credentials tab in the Mailgun admin portal.
- Create a new user with a username (e.g.,
blog@mg.mydomain.com
) and a secure password.
Step 4: Configure Ghost to Use Mailgun
Update your docker-compose.yml
file with the Mailgun SMTP configuration:
version: '3.1'
services:
ghost:
image: ghost:latest
container_name: blog_ghost
restart: always
ports:
- "2368:2368"
environment:
url: http://blog.mydomain.com
database__client: sqlite3
database__connection__filename: /var/lib/ghost/content/data/ghost.db
mail__transport: SMTP
mail__options__host: smtp.mailgun.org
mail__options__port: 465 # Use 465 for SSL
mail__options__secure: true
mail__options__auth__user: blog@mg.mydomain.com
mail__options__auth__pass: your_mailgun_password
mail__from: 'My Blog <username@mydomain.com>'
volumes:
- /home/username/projects/ghost_blog:/var/lib/ghost/content
Key Points:
- SSL and Port 465: Ensures secure email sending.
- Environment Variables: Sensitive details like passwords should ideally be stored as environment variables for better security.
Step 5: Test Your Configuration
- Restart Ghost to apply the changes:
sudo docker compose down
sudo docker compose up -d
- Send a test email from Ghost's admin panel (under Settings → Email) to confirm that the Mailgun setup works.
With Mailgun configured, your Ghost blog is ready to send newsletters.
Final Steps
You’re almost there! Open your browser and visit your new site.
To access the Ghost admin portal, simply add /ghost
to your domain URL:
The first time you log in, you’ll be prompted to create your admin account. Follow the on-screen instructions, and you’re ready to start managing your blog!
Conclusion
That’s it! Hopefully, if you went trough all of the steps of this process, you will’ have a Ghost app running on your VPS. Now, you can customize your new blog however you want.
In an upcoming guide, we’ll cover how to personalize your site and set up the marketing tools to grow your audience.
If you have any questions or feedback, feel free to reach out—I’d love to hear from you!
P.S. If you enjoyed this guide, don’t forget to subscribe to my newsletter for more tutorials, tips, and updates. 🚀