iTranslated by AI
The Ultimate Docker Setup: Running Django and Ghost Together on a Single VPS
Introduction
For individual projects or in the early phases of a startup, there is often a need to operate both a custom Web application (Django) and an owned media site (Ghost) at low cost.
This is a memo of how I built a deployment environment on ConoHa VPS using a Docker environment with a powerful reverse proxy container called HTTPS-Portal to meet the following requirements.
Achieved Architecture and Benefits
Architecture Diagram
- VPS: ConoHa VPS
- Frontend proxy: HTTPS-Portal (Nginx + Let's Encrypt auto-renewal)
- App A: Django + PostgreSQL (The Web app itself)
- App B: Ghost + MySQL (CMS/Blog)
Benefits of this architecture
- Automated SSL Certificate Renewal: HTTPS-Portal handles the acquisition and renewal of Let's Encrypt certificates fully automatically.
- Multi-domain support: Domain-based routing is possible simply by writing a single line of environment variables.
- High-speed delivery of static files: Django static files are served directly from the Nginx (HTTPS-Portal) side, reducing the load on the application.
Overview of docker-compose.yml
The following is the definition file created for this setup.
services:
# --- Django App Section ---
db:
image: postgres:15.2
container_name: postgres
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: pg_isready -U "${POSTGRES_USER:-postgres}" || exit 1
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
env_file:
- .env
app:
container_name: app
build:
context: .
dockerfile: Dockerfile
volumes:
- ./static:/app/static
- ./media:/app/media
expose:
- "8000"
command: sh -c "python manage.py migrate && gunicorn mySite.wsgi:application --bind 0.0.0.0:8000"
env_file:
- .env
depends_on:
db:
condition: service_healthy
# --- Ghost CMS Section ---
ghost-db:
image: mysql:8.0
container_name: ghost-db
restart: always
environment:
MYSQL_ROOT_PASSWORD: ${GHOST_DB_ROOT_PASSWORD}
MYSQL_DATABASE: ghost
MYSQL_USER: ghost
MYSQL_PASSWORD: ${GHOST_DB_PASSWORD}
volumes:
- ghost_db_data:/var/lib/mysql
ghost:
image: ghost:6.10.2
container_name: ghost
restart: always
depends_on:
- ghost-db
ports:
- "2368:2368"
environment:
database__client: mysql
database__connection__host: ghost-db
database__connection__user: ghost
database__connection__password: ${GHOST_DB_PASSWORD}
database__connection__database: ghost
url: sampleA_Ghost.com # Change to your actual production domain
NODE_ENV: production
volumes:
- ghost_content:/var/lib/ghost/content
# --- Proxy / SSL Section ---
https-portal:
image: steveltn/https-portal:1
container_name: https-portal
ports:
- "80:80"
- "443:443"
environment:
# Define domains and target containers here
DOMAINS: 'sampleA_Ghost.com -> http://ghost:2368, sampleB_Django.com -> http://app:8000'
STAGE: 'production' # Use 'production' for live, 'staging' for testing
volumes:
# Mount points to serve Django static files from Nginx
- ./static:/var/www/vhosts/sampleB_Django.com/static
- ./media:/var/www/vhosts/sampleB_Django.com/media
- https-portal-data:/var/lib/https-portal
depends_on:
- app
- ghost
volumes:
db_data:
https-portal-data:
ghost_content:
ghost_db_data:
Key Configuration Points
I will explain three important points for keeping this configuration running stably.
1. Routing Settings via HTTPS-Portal
The DOMAINS environment variable in the https-portal container is the core of the routing.
DOMAINS: 'your-media-site.com -> http://ghost:2368, your-app-site.com -> http://app:8000'
By simply writing the configuration in a comma-separated format like this, you can complete the reverse proxy settings per domain and obtain SSL certificates without having to edit Nginx configuration files.
2. Django Static File Serving (Important)
When running Django in a production environment (Gunicorn), static files such as CSS and images are not served by Django itself. Usually, you need to rely on Nginx or a similar tool.
In this configuration, we solve this by utilizing Docker Volume sharing.
- Django container: Collect files into
./staticusingcollectstatic. - HTTPS-Portal container: Mount the same
./staticto/var/www/vhosts/domain-name/static.
Since HTTPS-Portal (the internal Nginx) is designed to prioritize files located under /var/www/vhosts/domain-name/ by default, this allows Nginx to serve static files at high speed without passing through Django.
Reflecting Static Files
Immediately after starting the containers, the Django static files (CSS and JavaScript) will not exist on the volume, so the layout of the admin panel and other pages may appear broken.
Run the following command in the terminal on the VPS to gather the static files in one place:
docker-compose exec app python manage.py collectstatic --noinput
3. Ghost URL Setting
You also need to correctly configure the url environment variable on the Ghost side. If this mismatches the actual public URL, it will cause broken links or image paths within your articles.
environment:
url: sampleA_Ghost.com
Deployment Steps
- Preparing Environment Variables (.env)
For security reasons, passwords and similar credentials should be written in a.envfile.
POSTGRES_USER=myuser
POSTGRES_PASSWORD=mypassword
POSTGRES_DB=mydb
GHOST_DB_ROOT_PASSWORD=rootpass
GHOST_DB_PASSWORD=ghostpass
- Adjusting Django Settings
On the Django side, configure the output destination for static files and the settings for trusted proxies.
# settings.py
STATIC_ROOT = BASE_DIR / 'static'
MEDIA_ROOT = BASE_DIR / 'media'
# Setting to trust headers from HTTPS-Portal
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
ALLOWED_HOSTS = ['your-app-site.com']
- Starting and Initializing Containers
# Build and start
docker-compose up -d --build
# Gather Django static files
docker-compose exec app python manage.py collectstatic --noinput
With this, after waiting a few minutes, the Let's Encrypt certificates will be issued, and both sites will become accessible via HTTPS.
Summary
Using a single VPS to host both a Python-based Web application and a Node.js-based CMS is a highly effective way to keep initial costs down. By utilizing https-portal, you can free yourself from complex Nginx configuration and certificate management, allowing you to focus on the development itself.
Discussion