> ## Content Index
> Fetch the complete content index at: https://notes.d4rkcyber.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# D4rk Notes
- URL: https://notes.d4rkcyber.com/d4rk-notes-project/
- Published: 2026-06-02T00:00:00.000Z
- Updated: 2026-10-29T12:01:50.000Z
- Description: How I moved D4rk Notes from a custom Next.js/MDX site to Ghost 6 with MySQL, Docker Compose, persistent content storage, and Resend SMTP.
- Author: David Dami
- Tags: Projects, project, knowledge-base, mdx

Moving the Blog to Ghost and MySQL

D4rk Notes has moved from the original Next.js/MDX publishing setup to Ghost backed by MySQL. I wanted a straightforward publishing workflow, a theme I can control, and persistent storage for both posts and uploads. This is how the current deployment is put together.

## The stack

The site runs as two Docker Compose services:

- **Ghost 6** serves the blog and admin interface.
- **MySQL 8.0.44** stores Ghost’s content database.
- **Traefik** routes the public hostname to Ghost over the shared `proxy` Docker network.
- **Resend SMTP** handles outbound mail, including Ghost notifications.

The public URL is `https://notes.d4rkcyber.com`. Ghost is configured with that canonical URL so generated links and redirects use HTTPS. The Ghost container is attached to the external `proxy` network and a separate internal `backend` network. MySQL is only on `backend`, so it is not exposed through the reverse proxy or a published host port.

## Persistent data and theme

Ghost’s content directory is bind-mounted to persistent storage. That directory contains uploaded images and other content files. The D4rk Notes theme is mounted separately into Ghost’s themes directory, which lets me edit the theme on the host without baking it into a new image.

MySQL has its own persistent data directory. The database and Ghost content uploads are separate, so both need to be included in backups. A database dump alone would not preserve uploaded images; copying only the content directory would not preserve posts and settings stored in MySQL.

## Startup and health

Compose waits for MySQL’s health check before starting Ghost. The health check uses `mysqladmin ping`, which avoids bringing Ghost up while the database is still initializing. Both services restart unless stopped, and the database image is pinned to a specific MySQL 8.0.44 digest for repeatable deployments.

## Mail

Ghost sends mail through Resend’s SMTP endpoint using STARTTLS on port 587\. The sender address and SMTP credentials are configured through environment variables. The actual SMTP password/API key belongs in the server’s private `.env` file and should never be committed or copied into a public Compose example.

## Compose file

This is the deployment structure. The `.env` file supplies database credentials, storage locations, the public domain, and mail settings. The proxy network must already exist. No host `ports:` mapping is needed for Ghost when Traefik shares that network.

```yaml
services:
  ghost:
    image: ghost:${GHOST_VERSION:-6-alpine}
    container_name: d4rknotes
    restart: unless-stopped
    env_file:
      - .env
    environment:
      NODE_ENV: production
      url: https://${DOMAIN}
      database__client: mysql
      database__connection__host: d4rknotes-ghost-mysql
      database__connection__user: ${DATABASE_USER}
      database__connection__password: ${DATABASE_PASSWORD}
      database__connection__database: ghost
    volumes:
      - ${UPLOAD_LOCATION:-./data/ghost}:/var/lib/ghost/content
      - ./themes/d4rknotes:/var/lib/ghost/content/themes/d4rknotes
    depends_on:
      db:
        condition: service_healthy
    networks:
      - proxy
      - backend

  db:
    image: mysql:8.0.44@sha256:f37951fc3753a6a22d6c7bf6978c5e5fefcf6f31814d98c582524f98eae52b21
    container_name: ghost_db
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${DATABASE_ROOT_PASSWORD}
      MYSQL_USER: ${DATABASE_USER}
      MYSQL_PASSWORD: ${DATABASE_PASSWORD}
      MYSQL_DATABASE: ghost
    volumes:
      - ${MYSQL_DATA_LOCATION}:/var/lib/mysql
    healthcheck:
      test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -p\"$$MYSQL_ROOT_PASSWORD\""]
      interval: 10s
      timeout: 5s
      start_period: 30s
      retries: 12
    networks:
      backend:
        aliases:
          - d4rknotes-ghost-mysql

networks:
  proxy:
    external: true
    name: proxy
  backend:
    internal: true

```

The `.env` file is intentionally not included. At minimum it needs values for `DOMAIN`, `DATABASE_USER`, `DATABASE_PASSWORD`, `DATABASE_ROOT_PASSWORD`, `MYSQL_DATA_LOCATION`, `UPLOAD_LOCATION`, and the Ghost mail settings. Keep strong, unique database passwords and back up both persistent data locations.

## Why Ghost

The original site used a custom Next.js and MDX content pipeline. Ghost replaces that with a purpose-built publishing system and a MySQL-backed admin workflow, while the custom theme keeps the site’s presentation under my control. The result is less bespoke publishing infrastructure to maintain and a clearer separation between the application, database, uploads, and theme.