Vibecode Ghost Pro
track this build8 phases, 18 steps, beginner friendly0%Because Ghost is open source, the core publishing/newsletter/membership product can be self-hosted; the cost is ops, email setup, and maintenance.
You are building a lean indie version of Ghost Pro. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Ghost Pro · indie build Ghost, self-hosted properly: the open-source CMS in Docker with MySQL 8, Caddy for HTTPS, transactional email over SMTP, newsletters through Mailgun (the only bulk provider Ghost supports self-hosted), memberships wired to Stripe in test mode first, and a backup you have actually restored once. You save the subscription and take on upgrades and deliverability. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | CMS | The official ghost Docker image | rule zero: do not write a CMS | | Database | MySQL 8.0 or 8.4 in Docker | the only database Ghost supports in production; MariaDB and SQLite are not | | TLS | Caddy | automatic certificates, one config file | | Newsletters | Mailgun | Ghost's bulk sending is built on Mailgun's API specifically | | Payments | Stripe | Ghost's native memberships integration | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A terminal and a code editor** · free - Why: Every step below is a command you type or a file you edit. - Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. - Verify: You can open a folder and run a command in its terminal - [ ] **Git** · free - Why: History for your code, and the way most hosts deploy. - Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. - Verify: git --version prints a version - [ ] **A VPS with at least 2 GB of RAM** · about $6 to $12 a month - Why: Ghost plus MySQL need it. Ubuntu 24.04 with SSH. - Get it: Hetzner CX22 or a DigitalOcean 2 GB droplet. - [ ] **Docker Engine and Compose on the VPS** · free - Why: Ghost and MySQL run as containers. - Get it: Follow docs.docker.com/engine/install/ubuntu, then add your user to the docker group. - Verify: docker compose version prints a version - [ ] **A domain or subdomain** (optional) · roughly $10 a year, or free on an existing domain - Why: Ghost's url setting must be the exact public HTTPS address. - Get it: Register at Cloudflare Registrar, Porkbun or Namecheap, or use a subdomain of one you already own. You add one DNS record in the deploy phase. - [ ] **SMTP credentials for transactional mail** · free tiers exist - Why: Password resets, staff invites and member sign-in links. Any SMTP provider. - Get it: Postmark, Fastmail or Mailgun's SMTP: host, port, username, password. - [ ] **A Mailgun account, API key and sending domain** · free trial, then usage-based - Why: Newsletters will not send over SMTP. Ghost's bulk email is Mailgun-only for self-hosted installs as of 2026. - Get it: Sign up at mailgun.com, add a sending domain (mail.yourdomain.com), add the DNS records it shows (SPF, DKIM, MX), then Settings > API Keys > create a key. Verify current provider support in Ghost's docs before relying on this. - [ ] **A Stripe account with test-mode keys** (optional) · free; fees per transaction when live - Why: Paid memberships. Test mode first, live as a separate deliberate step. - Get it: dashboard.stripe.com > Developers > API keys (toggle Test mode). Ghost connects via Stripe Connect from its settings. - [ ] **Somewhere off the box for backups** · cents a month - Why: A backup on the server being backed up is not a backup. - Get it: Backblaze B2 or Cloudflare R2 bucket plus rclone configured on the VPS. ## Quick start ```sh docker compose up -d docker compose logs -f ghost ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - A custom theme from scratch and any custom blog engine code. - Third-party analytics. - A CDN and WAF in front; worth adding later. - managed hosting - upgrades - backups - email delivery setup - CDN/security - support - easy scaling If one of those is essential to you, that is the reason to keep paying for Ghost Pro, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Ghost Pro The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a self-hosted Ghost site to replace Ghost Pro. Build it in phases, in the order below. This is an operations task, not a coding task · each phase ends in a verification you actually run, and Phase 7 is the one people skip and regret. ### Rule zero Do not write a CMS. Deploy the open-source Ghost. Any time spent building blog software here is time not spent on the parts that actually break: email deliverability, backups and upgrades. ### Stack (fixed, do not substitute) - Docker Compose: the official `ghost` image and MySQL 8. Ghost requires MySQL 8.0 or 8.4 in production · MariaDB and SQLite are not supported there, and substituting one is a migration you will discover at the worst moment. - Caddy in front for automatic HTTPS. Ghost binds to localhost only. - Named volumes for Ghost content and the database. ### Phase 1 · Bring it up locally Build: `docker-compose.yml` with both services, health checks, a restart policy, and every secret read from `.env` (never inline). Run it locally first, on `http://localhost`. Done when: the site loads, `/ghost` reaches the admin setup screen, and `docker compose down && docker compose up -d` preserves the database and content. Do not build yet: DNS, TLS, email, Stripe. ### Phase 2 · Domain and TLS Build: the Caddy config for the real domain with automatic certificates, Ghost bound to localhost so only Caddy is exposed, and `url` set to the public HTTPS address in Ghost's config. Getting `url` wrong produces a site that loads but generates broken links and mixed content, and it is not obvious from the homepage. Done when: the public domain serves over HTTPS with a valid certificate, HTTP redirects to HTTPS, `curl` against the server's IP on Ghost's port from another machine is refused, and no page contains a mixed-content warning. ### Phase 3 · Transactional email Build: SMTP credentials in `.env` for Ghost's transactional mail · password resets, staff invites, member signin links. Done when: a member magic link and a staff invite both arrive in a real inbox, not just in the container log. Do not build yet: newsletters. They are a different system, see Phase 4. ### Phase 4 · Newsletter delivery Build: bulk email through Mailgun. This is the constraint that surprises people: Ghost's newsletter sending is built on Mailgun's API specifically, and as of 2026 it remains the only first-class bulk provider for a self-hosted install. Plain SMTP delivers your password resets and will not deliver your newsletter. Configure the Mailgun API key and domain, and set up the DNS records · SPF, DKIM and DMARC · before the first send. Done when: a test newsletter reaches a real subscriber at a major provider, lands in the inbox rather than spam, and a mail-tester style check passes SPF, DKIM and DMARC. Verify the current provider support in Ghost's docs before building this · it is the item most likely to have changed. ### Phase 5 · Memberships and payments Build: Stripe keys in `.env`, test mode first. Configure tiers, verify the signup and checkout flow end to end, then switch to live keys as a separate deliberate step. Done when: a test-mode subscription completes, the member appears with the right tier, a paid post is gated for a free member and readable for a paid one, and cancelling in Stripe revokes access. ### Phase 6 · Theme Build: start from the default theme and adjust. Do not write one from scratch in this project. Done when: the site renders correctly on mobile, and the theme survives a Ghost restart without a re-upload. ### Phase 7 · Backup, and a restore drill Build: a nightly cron that runs `mysqldump` and tars the content volume into `./backups/`, keeping 14 days, copied off the machine · a backup that only exists on the server being backed up is not a backup. Then do the part that matters: restore into a fresh throwaway container and confirm the site comes up with the posts, members and images intact. Done when: you have actually completed one restore into a clean environment and the restored site is complete. Not "the script runs" · a restore you have performed once, with the date written in the README. ### Phase 8 · Upgrades and handover Build: an `update.sh` that pulls new images and restarts, with a note to read the release notes before major-version jumps and to take a backup first. Then the README. Done when: one upgrade cycle has been run and the site is still up. ### Out of scope (and why) - A custom theme from scratch, and any custom blog engine code. - Third-party analytics. - A CDN and WAF in front. Worth adding later; not what makes this work on day one. ### README must contain - Every DNS record, including the email ones, with what breaks if each is missing. - The date of the last successful restore drill. - The honest trade, stated plainly: you are saving the subscription and taking on upgrades, patching, deliverability and being your own support. That is the deal. ===== AGENTS.md ===== # Agent instructions · Ghost Pro indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: The official ghost Docker image, MySQL 8.0 or 8.4 in Docker, Caddy, Mailgun, Stripe. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - MySQL 8.0 or 8.4 only. Substituting MariaDB or SQLite is a migration you discover at the worst moment. - Plain SMTP delivers your password resets and will not deliver your newsletter. These are two systems. ===== BUILD_PLAN.md ===== # Build plan · Ghost Pro Ghost, self-hosted properly: the open-source CMS in Docker with MySQL 8, Caddy for HTTPS, transactional email over SMTP, newsletters through Mailgun (the only bulk provider Ghost supports self-hosted), memberships wired to Stripe in test mode first, and a backup you have actually restored once. You save the subscription and take on upgrades and deliverability. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Bring it up locally Ghost and MySQL running from one compose file, data surviving a restart. ### Steps 1. Write docker-compose.yml with ghost and mysql:8 Named volumes for content and the database, health checks, restart: unless-stopped, every secret from .env. Files: `docker-compose.yml`, `.env` 2. Start it and reach the setup screen ```sh docker compose up -d docker compose logs -f ghost ``` ### Done when - [ ] The site loads on localhost - [ ] /ghost reaches the admin setup screen - [ ] docker compose down && docker compose up -d preserves posts and settings ### Watch out - MySQL 8.0 or 8.4 only. Substituting MariaDB or SQLite is a migration you discover at the worst moment. ## Phase 2 · Domain and TLS Public HTTPS, Ghost bound to localhost, url set correctly. ### Steps 1. Write the Caddyfile and set GHOST_URL Your domain with reverse_proxy localhost:2368. Ghost's port published on 127.0.0.1 only. Files: `Caddyfile` 2. Point DNS and verify from another machine ### Done when - [ ] HTTPS with a valid certificate; HTTP redirects - [ ] curl to the server IP on 2368 from outside is refused - [ ] No page shows a mixed-content warning ## Phase 3 · Transactional email Magic links and invites arrive in a real inbox. ### Steps 1. Set the mail__ environment variables from your SMTP provider and restart mail__transport, mail__options__host, port, auth user and pass, mail__from. 2. Send a staff invite and a member magic link and read both in a real inbox Check the spam folder too; if they land there, fix SPF for the from domain before moving on. ### Done when - [ ] A member magic link arrives - [ ] A staff invite arrives ## Phase 4 · Newsletter delivery Bulk email through Mailgun with DNS that keeps it out of spam. ### Steps 1. Add SPF, DKIM and DMARC records for the sending domain Mailgun shows the exact records. DMARC starts at p=none. 2. Enter the Mailgun API key and domain in Ghost's Email newsletter settings 3. Send a test newsletter to a Gmail and an Outlook address ### Done when - [ ] The test newsletter reaches the inbox at a major provider, not spam - [ ] A mail-tester style check passes SPF, DKIM and DMARC ### Watch out - Plain SMTP delivers your password resets and will not deliver your newsletter. These are two systems. ## Phase 5 · Memberships and payments Tiers and gating working in test mode before a single live key exists. ### Steps 1. Connect Stripe in test mode and create tiers 2. Run a test subscription and gate a post ### Done when - [ ] A test-mode subscription completes and the member shows the right tier - [ ] A paid post is gated for a free member and readable for a paid one - [ ] Cancelling in Stripe revokes access ## Phase 6 · Theme The default theme adjusted, not a theme from scratch. ### Steps 1. Adjust the default theme's settings and upload any small CSS changes 2. Confirm it survives a restart ### Done when - [ ] Renders correctly on mobile - [ ] The theme survives docker compose restart ## Phase 7 · Backup, and a restore drill A nightly backup off the box and one restore you actually performed. ### Steps 1. Write backup.sh: mysqldump plus a tar of the content volume, to ./backups, fourteen kept, copied off the box Files: `backup.sh` ```sh docker compose exec -T mysql mysqldump -u root -p$MYSQL_ROOT_PASSWORD ghost > backups/ghost-$(date +%F).sql docker run --rm -v ghost_content:/c -v $PWD/backups:/b alpine tar czf /b/content-$(date +%F).tgz -C /c . rclone copy backups remote:ghost-backups/ ``` 2. Schedule it nightly with cron 3. Restore into a throwaway compose stack and check posts, members and images Do this now, not later. Write the date in the README. ### Done when - [ ] Last night's backup exists off the box - [ ] A restore into a clean stack brings up the site with posts, members and images intact - [ ] The restore date is written in the README ## Phase 8 · Upgrades and handover A repeatable upgrade and the honest trade written down. ### Steps 1. Write update.sh: backup, pull, up -d, verify Files: `update.sh` 2. Write the README Every DNS record and what breaks without it, the restore date, and the trade: you now own upgrades, patching, deliverability and support. Files: `README.md` ### Done when - [ ] One upgrade cycle has been run and the site is up - [ ] The README lists every DNS record ## Not in this build - A custom theme from scratch and any custom blog engine code. - Third-party analytics. - A CDN and WAF in front; worth adding later. ## After v1, if you want it - Cloudflare in front for caching and a WAF - A staging stack from last night's backup to test upgrades on ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. The exact public address. Wrong here means broken links and mixed content. GHOST_URL=https://yourdomain.com # Required · secret. openssl rand -base64 24. MYSQL_ROOT_PASSWORD=long-random # Required · secret. Ghost's database password. MYSQL_PASSWORD=long-random # Required. Ghost transactional mail transport. MAIL_TRANSPORT=SMTP # Required. From your SMTP provider. MAIL_HOST=smtp.postmarkapp.com # Required. From your SMTP provider. MAIL_PORT=587 # Required · secret. SMTP username. MAIL_USER=... # Required · secret. SMTP password. MAIL_PASS=... # Required. From address for transactional mail. MAIL_FROM='Your Site' <hello@yourdomain.com>
You are building a lean indie version of Ghost Pro. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Ghost Pro · indie build Ghost, self-hosted properly: the open-source CMS in Docker with MySQL 8, Caddy for HTTPS, transactional email over SMTP, newsletters through Mailgun (the only bulk provider Ghost supports self-hosted), memberships wired to Stripe in test mode first, and a backup you have actually restored once. You save the subscription and take on upgrades and deliverability. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | CMS | The official ghost Docker image | rule zero: do not write a CMS | | Database | MySQL 8.0 or 8.4 in Docker | the only database Ghost supports in production; MariaDB and SQLite are not | | TLS | Caddy | automatic certificates, one config file | | Newsletters | Mailgun | Ghost's bulk sending is built on Mailgun's API specifically | | Payments | Stripe | Ghost's native memberships integration | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A terminal and a code editor** · free - Why: Every step below is a command you type or a file you edit. - Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. - Verify: You can open a folder and run a command in its terminal - [ ] **Git** · free - Why: History for your code, and the way most hosts deploy. - Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. - Verify: git --version prints a version - [ ] **A VPS with at least 2 GB of RAM** · about $6 to $12 a month - Why: Ghost plus MySQL need it. Ubuntu 24.04 with SSH. - Get it: Hetzner CX22 or a DigitalOcean 2 GB droplet. - [ ] **Docker Engine and Compose on the VPS** · free - Why: Ghost and MySQL run as containers. - Get it: Follow docs.docker.com/engine/install/ubuntu, then add your user to the docker group. - Verify: docker compose version prints a version - [ ] **A domain or subdomain** (optional) · roughly $10 a year, or free on an existing domain - Why: Ghost's url setting must be the exact public HTTPS address. - Get it: Register at Cloudflare Registrar, Porkbun or Namecheap, or use a subdomain of one you already own. You add one DNS record in the deploy phase. - [ ] **SMTP credentials for transactional mail** · free tiers exist - Why: Password resets, staff invites and member sign-in links. Any SMTP provider. - Get it: Postmark, Fastmail or Mailgun's SMTP: host, port, username, password. - [ ] **A Mailgun account, API key and sending domain** · free trial, then usage-based - Why: Newsletters will not send over SMTP. Ghost's bulk email is Mailgun-only for self-hosted installs as of 2026. - Get it: Sign up at mailgun.com, add a sending domain (mail.yourdomain.com), add the DNS records it shows (SPF, DKIM, MX), then Settings > API Keys > create a key. Verify current provider support in Ghost's docs before relying on this. - [ ] **A Stripe account with test-mode keys** (optional) · free; fees per transaction when live - Why: Paid memberships. Test mode first, live as a separate deliberate step. - Get it: dashboard.stripe.com > Developers > API keys (toggle Test mode). Ghost connects via Stripe Connect from its settings. - [ ] **Somewhere off the box for backups** · cents a month - Why: A backup on the server being backed up is not a backup. - Get it: Backblaze B2 or Cloudflare R2 bucket plus rclone configured on the VPS. ## Quick start ```sh docker compose up -d docker compose logs -f ghost ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - A custom theme from scratch and any custom blog engine code. - Third-party analytics. - A CDN and WAF in front; worth adding later. - managed hosting - upgrades - backups - email delivery setup - CDN/security - support - easy scaling If one of those is essential to you, that is the reason to keep paying for Ghost Pro, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Ghost Pro The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a self-hosted Ghost site to replace Ghost Pro. Build it in phases, in the order below. This is an operations task, not a coding task · each phase ends in a verification you actually run, and Phase 7 is the one people skip and regret. ### Rule zero Do not write a CMS. Deploy the open-source Ghost. Any time spent building blog software here is time not spent on the parts that actually break: email deliverability, backups and upgrades. ### Stack (fixed, do not substitute) - Docker Compose: the official `ghost` image and MySQL 8. Ghost requires MySQL 8.0 or 8.4 in production · MariaDB and SQLite are not supported there, and substituting one is a migration you will discover at the worst moment. - Caddy in front for automatic HTTPS. Ghost binds to localhost only. - Named volumes for Ghost content and the database. ### Phase 1 · Bring it up locally Build: `docker-compose.yml` with both services, health checks, a restart policy, and every secret read from `.env` (never inline). Run it locally first, on `http://localhost`. Done when: the site loads, `/ghost` reaches the admin setup screen, and `docker compose down && docker compose up -d` preserves the database and content. Do not build yet: DNS, TLS, email, Stripe. ### Phase 2 · Domain and TLS Build: the Caddy config for the real domain with automatic certificates, Ghost bound to localhost so only Caddy is exposed, and `url` set to the public HTTPS address in Ghost's config. Getting `url` wrong produces a site that loads but generates broken links and mixed content, and it is not obvious from the homepage. Done when: the public domain serves over HTTPS with a valid certificate, HTTP redirects to HTTPS, `curl` against the server's IP on Ghost's port from another machine is refused, and no page contains a mixed-content warning. ### Phase 3 · Transactional email Build: SMTP credentials in `.env` for Ghost's transactional mail · password resets, staff invites, member signin links. Done when: a member magic link and a staff invite both arrive in a real inbox, not just in the container log. Do not build yet: newsletters. They are a different system, see Phase 4. ### Phase 4 · Newsletter delivery Build: bulk email through Mailgun. This is the constraint that surprises people: Ghost's newsletter sending is built on Mailgun's API specifically, and as of 2026 it remains the only first-class bulk provider for a self-hosted install. Plain SMTP delivers your password resets and will not deliver your newsletter. Configure the Mailgun API key and domain, and set up the DNS records · SPF, DKIM and DMARC · before the first send. Done when: a test newsletter reaches a real subscriber at a major provider, lands in the inbox rather than spam, and a mail-tester style check passes SPF, DKIM and DMARC. Verify the current provider support in Ghost's docs before building this · it is the item most likely to have changed. ### Phase 5 · Memberships and payments Build: Stripe keys in `.env`, test mode first. Configure tiers, verify the signup and checkout flow end to end, then switch to live keys as a separate deliberate step. Done when: a test-mode subscription completes, the member appears with the right tier, a paid post is gated for a free member and readable for a paid one, and cancelling in Stripe revokes access. ### Phase 6 · Theme Build: start from the default theme and adjust. Do not write one from scratch in this project. Done when: the site renders correctly on mobile, and the theme survives a Ghost restart without a re-upload. ### Phase 7 · Backup, and a restore drill Build: a nightly cron that runs `mysqldump` and tars the content volume into `./backups/`, keeping 14 days, copied off the machine · a backup that only exists on the server being backed up is not a backup. Then do the part that matters: restore into a fresh throwaway container and confirm the site comes up with the posts, members and images intact. Done when: you have actually completed one restore into a clean environment and the restored site is complete. Not "the script runs" · a restore you have performed once, with the date written in the README. ### Phase 8 · Upgrades and handover Build: an `update.sh` that pulls new images and restarts, with a note to read the release notes before major-version jumps and to take a backup first. Then the README. Done when: one upgrade cycle has been run and the site is still up. ### Out of scope (and why) - A custom theme from scratch, and any custom blog engine code. - Third-party analytics. - A CDN and WAF in front. Worth adding later; not what makes this work on day one. ### README must contain - Every DNS record, including the email ones, with what breaks if each is missing. - The date of the last successful restore drill. - The honest trade, stated plainly: you are saving the subscription and taking on upgrades, patching, deliverability and being your own support. That is the deal. ===== AGENTS.md ===== # Agent instructions · Ghost Pro indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: The official ghost Docker image, MySQL 8.0 or 8.4 in Docker, Caddy, Mailgun, Stripe. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - MySQL 8.0 or 8.4 only. Substituting MariaDB or SQLite is a migration you discover at the worst moment. - Plain SMTP delivers your password resets and will not deliver your newsletter. These are two systems. ===== BUILD_PLAN.md ===== # Build plan · Ghost Pro Ghost, self-hosted properly: the open-source CMS in Docker with MySQL 8, Caddy for HTTPS, transactional email over SMTP, newsletters through Mailgun (the only bulk provider Ghost supports self-hosted), memberships wired to Stripe in test mode first, and a backup you have actually restored once. You save the subscription and take on upgrades and deliverability. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Bring it up locally Ghost and MySQL running from one compose file, data surviving a restart. ### Steps 1. Write docker-compose.yml with ghost and mysql:8 Named volumes for content and the database, health checks, restart: unless-stopped, every secret from .env. Files: `docker-compose.yml`, `.env` 2. Start it and reach the setup screen ```sh docker compose up -d docker compose logs -f ghost ``` ### Done when - [ ] The site loads on localhost - [ ] /ghost reaches the admin setup screen - [ ] docker compose down && docker compose up -d preserves posts and settings ### Watch out - MySQL 8.0 or 8.4 only. Substituting MariaDB or SQLite is a migration you discover at the worst moment. ## Phase 2 · Domain and TLS Public HTTPS, Ghost bound to localhost, url set correctly. ### Steps 1. Write the Caddyfile and set GHOST_URL Your domain with reverse_proxy localhost:2368. Ghost's port published on 127.0.0.1 only. Files: `Caddyfile` 2. Point DNS and verify from another machine ### Done when - [ ] HTTPS with a valid certificate; HTTP redirects - [ ] curl to the server IP on 2368 from outside is refused - [ ] No page shows a mixed-content warning ## Phase 3 · Transactional email Magic links and invites arrive in a real inbox. ### Steps 1. Set the mail__ environment variables from your SMTP provider and restart mail__transport, mail__options__host, port, auth user and pass, mail__from. 2. Send a staff invite and a member magic link and read both in a real inbox Check the spam folder too; if they land there, fix SPF for the from domain before moving on. ### Done when - [ ] A member magic link arrives - [ ] A staff invite arrives ## Phase 4 · Newsletter delivery Bulk email through Mailgun with DNS that keeps it out of spam. ### Steps 1. Add SPF, DKIM and DMARC records for the sending domain Mailgun shows the exact records. DMARC starts at p=none. 2. Enter the Mailgun API key and domain in Ghost's Email newsletter settings 3. Send a test newsletter to a Gmail and an Outlook address ### Done when - [ ] The test newsletter reaches the inbox at a major provider, not spam - [ ] A mail-tester style check passes SPF, DKIM and DMARC ### Watch out - Plain SMTP delivers your password resets and will not deliver your newsletter. These are two systems. ## Phase 5 · Memberships and payments Tiers and gating working in test mode before a single live key exists. ### Steps 1. Connect Stripe in test mode and create tiers 2. Run a test subscription and gate a post ### Done when - [ ] A test-mode subscription completes and the member shows the right tier - [ ] A paid post is gated for a free member and readable for a paid one - [ ] Cancelling in Stripe revokes access ## Phase 6 · Theme The default theme adjusted, not a theme from scratch. ### Steps 1. Adjust the default theme's settings and upload any small CSS changes 2. Confirm it survives a restart ### Done when - [ ] Renders correctly on mobile - [ ] The theme survives docker compose restart ## Phase 7 · Backup, and a restore drill A nightly backup off the box and one restore you actually performed. ### Steps 1. Write backup.sh: mysqldump plus a tar of the content volume, to ./backups, fourteen kept, copied off the box Files: `backup.sh` ```sh docker compose exec -T mysql mysqldump -u root -p$MYSQL_ROOT_PASSWORD ghost > backups/ghost-$(date +%F).sql docker run --rm -v ghost_content:/c -v $PWD/backups:/b alpine tar czf /b/content-$(date +%F).tgz -C /c . rclone copy backups remote:ghost-backups/ ``` 2. Schedule it nightly with cron 3. Restore into a throwaway compose stack and check posts, members and images Do this now, not later. Write the date in the README. ### Done when - [ ] Last night's backup exists off the box - [ ] A restore into a clean stack brings up the site with posts, members and images intact - [ ] The restore date is written in the README ## Phase 8 · Upgrades and handover A repeatable upgrade and the honest trade written down. ### Steps 1. Write update.sh: backup, pull, up -d, verify Files: `update.sh` 2. Write the README Every DNS record and what breaks without it, the restore date, and the trade: you now own upgrades, patching, deliverability and support. Files: `README.md` ### Done when - [ ] One upgrade cycle has been run and the site is up - [ ] The README lists every DNS record ## Not in this build - A custom theme from scratch and any custom blog engine code. - Third-party analytics. - A CDN and WAF in front; worth adding later. ## After v1, if you want it - Cloudflare in front for caching and a WAF - A staging stack from last night's backup to test upgrades on ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. The exact public address. Wrong here means broken links and mixed content. GHOST_URL=https://yourdomain.com # Required · secret. openssl rand -base64 24. MYSQL_ROOT_PASSWORD=long-random # Required · secret. Ghost's database password. MYSQL_PASSWORD=long-random # Required. Ghost transactional mail transport. MAIL_TRANSPORT=SMTP # Required. From your SMTP provider. MAIL_HOST=smtp.postmarkapp.com # Required. From your SMTP provider. MAIL_PORT=587 # Required · secret. SMTP username. MAIL_USER=... # Required · secret. SMTP password. MAIL_PASS=... # Required. From address for transactional mail. MAIL_FROM='Your Site' <hello@yourdomain.com>
You are building a production product version of Ghost Pro. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Ghost Pro · product brief ## Problem Because Ghost is open source, the core publishing/newsletter/membership product can be self-hosted; the cost is ops, email setup, and maintenance. ## Product outcome A publication platform you operate with confidence: monitored, backed up with a proven restore, upgradable in one command. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - VPS/Docker - domain/DNS - transactional/email provider - backups - theme/configuration ## Explicit non-goals for v1 - A custom theme from scratch and any custom blog engine code. - Third-party analytics. - A CDN and WAF in front; worth adding later. - managed hosting - upgrades - backups - email delivery setup - CDN/security - support - easy scaling ## Success criteria - Newsletter test lands in the inbox at two major providers - One restore drill performed and dated - Stripe live keys enabled only after a test-mode subscription and cancellation succeeded - update.sh run once successfully ===== BRIEF.md ===== # Build brief · Ghost Pro The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a self-hosted Ghost site to replace Ghost Pro. Build it in phases, in the order below. This is an operations task, not a coding task · each phase ends in a verification you actually run, and Phase 7 is the one people skip and regret. ### Rule zero Do not write a CMS. Deploy the open-source Ghost. Any time spent building blog software here is time not spent on the parts that actually break: email deliverability, backups and upgrades. ### Stack (fixed, do not substitute) - Docker Compose: the official `ghost` image and MySQL 8. Ghost requires MySQL 8.0 or 8.4 in production · MariaDB and SQLite are not supported there, and substituting one is a migration you will discover at the worst moment. - Caddy in front for automatic HTTPS. Ghost binds to localhost only. - Named volumes for Ghost content and the database. ### Phase 1 · Bring it up locally Build: `docker-compose.yml` with both services, health checks, a restart policy, and every secret read from `.env` (never inline). Run it locally first, on `http://localhost`. Done when: the site loads, `/ghost` reaches the admin setup screen, and `docker compose down && docker compose up -d` preserves the database and content. Do not build yet: DNS, TLS, email, Stripe. ### Phase 2 · Domain and TLS Build: the Caddy config for the real domain with automatic certificates, Ghost bound to localhost so only Caddy is exposed, and `url` set to the public HTTPS address in Ghost's config. Getting `url` wrong produces a site that loads but generates broken links and mixed content, and it is not obvious from the homepage. Done when: the public domain serves over HTTPS with a valid certificate, HTTP redirects to HTTPS, `curl` against the server's IP on Ghost's port from another machine is refused, and no page contains a mixed-content warning. ### Phase 3 · Transactional email Build: SMTP credentials in `.env` for Ghost's transactional mail · password resets, staff invites, member signin links. Done when: a member magic link and a staff invite both arrive in a real inbox, not just in the container log. Do not build yet: newsletters. They are a different system, see Phase 4. ### Phase 4 · Newsletter delivery Build: bulk email through Mailgun. This is the constraint that surprises people: Ghost's newsletter sending is built on Mailgun's API specifically, and as of 2026 it remains the only first-class bulk provider for a self-hosted install. Plain SMTP delivers your password resets and will not deliver your newsletter. Configure the Mailgun API key and domain, and set up the DNS records · SPF, DKIM and DMARC · before the first send. Done when: a test newsletter reaches a real subscriber at a major provider, lands in the inbox rather than spam, and a mail-tester style check passes SPF, DKIM and DMARC. Verify the current provider support in Ghost's docs before building this · it is the item most likely to have changed. ### Phase 5 · Memberships and payments Build: Stripe keys in `.env`, test mode first. Configure tiers, verify the signup and checkout flow end to end, then switch to live keys as a separate deliberate step. Done when: a test-mode subscription completes, the member appears with the right tier, a paid post is gated for a free member and readable for a paid one, and cancelling in Stripe revokes access. ### Phase 6 · Theme Build: start from the default theme and adjust. Do not write one from scratch in this project. Done when: the site renders correctly on mobile, and the theme survives a Ghost restart without a re-upload. ### Phase 7 · Backup, and a restore drill Build: a nightly cron that runs `mysqldump` and tars the content volume into `./backups/`, keeping 14 days, copied off the machine · a backup that only exists on the server being backed up is not a backup. Then do the part that matters: restore into a fresh throwaway container and confirm the site comes up with the posts, members and images intact. Done when: you have actually completed one restore into a clean environment and the restored site is complete. Not "the script runs" · a restore you have performed once, with the date written in the README. ### Phase 8 · Upgrades and handover Build: an `update.sh` that pulls new images and restarts, with a note to read the release notes before major-version jumps and to take a backup first. Then the README. Done when: one upgrade cycle has been run and the site is still up. ### Out of scope (and why) - A custom theme from scratch, and any custom blog engine code. - Third-party analytics. - A CDN and WAF in front. Worth adding later; not what makes this work on day one. ### README must contain - Every DNS record, including the email ones, with what breaks if each is missing. - The date of the last successful restore drill. - The honest trade, stated plainly: you are saving the subscription and taking on upgrades, patching, deliverability and being your own support. That is the deal. ===== ARCHITECTURE.md ===== # Architecture · Ghost Pro ## Stack | Part | Choice | Why | | --- | --- | --- | | CMS | The official ghost Docker image | rule zero: do not write a CMS | | Database | MySQL 8.0 or 8.4 in Docker | the only database Ghost supports in production; MariaDB and SQLite are not | | TLS | Caddy | automatic certificates, one config file | | Newsletters | Mailgun | Ghost's bulk sending is built on Mailgun's API specifically | | Payments | Stripe | Ghost's native memberships integration | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Ghost | content, members, newsletters | It is the product; you do not fork it | | MySQL | all state | Managed MySQL 8 later, same dump format | | Caddy | TLS and the public edge | Any reverse proxy or a CDN in front | | Mail | SMTP for transactional, Mailgun for bulk | SMTP provider freely; bulk provider only as Ghost adds support | | Backup | dump, tar, off-box copy, restore.sh | Any storage rclone speaks | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `GHOST_URL` · required · The exact public address. Wrong here means broken links and mixed content. - `MYSQL_ROOT_PASSWORD` · required, secret · openssl rand -base64 24. - `MYSQL_PASSWORD` · required, secret · Ghost's database password. - `MAIL_TRANSPORT` · required · Ghost transactional mail transport. - `MAIL_HOST` · required · From your SMTP provider. - `MAIL_PORT` · required · From your SMTP provider. - `MAIL_USER` · required, secret · SMTP username. - `MAIL_PASS` · required, secret · SMTP password. - `MAIL_FROM` · required · From address for transactional mail. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Ghost Pro product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: The official ghost Docker image, MySQL 8.0 or 8.4 in Docker, Caddy, Mailgun, Stripe. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - MySQL 8.0 or 8.4 only. Substituting MariaDB or SQLite is a migration you discover at the worst moment. - Plain SMTP delivers your password resets and will not deliver your newsletter. These are two systems. ===== MILESTONES.md ===== # Delivery milestones · Ghost Pro Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Bring it up locally Ghost and MySQL running from one compose file, data surviving a restart. ### Steps 1. Write docker-compose.yml with ghost and mysql:8 Named volumes for content and the database, health checks, restart: unless-stopped, every secret from .env. Files: `docker-compose.yml`, `.env` 2. Start it and reach the setup screen ```sh docker compose up -d docker compose logs -f ghost ``` ### Done when - [ ] The site loads on localhost - [ ] /ghost reaches the admin setup screen - [ ] docker compose down && docker compose up -d preserves posts and settings ### Watch out - MySQL 8.0 or 8.4 only. Substituting MariaDB or SQLite is a migration you discover at the worst moment. ## M2 · Domain and TLS Public HTTPS, Ghost bound to localhost, url set correctly. ### Steps 1. Write the Caddyfile and set GHOST_URL Your domain with reverse_proxy localhost:2368. Ghost's port published on 127.0.0.1 only. Files: `Caddyfile` 2. Point DNS and verify from another machine ### Done when - [ ] HTTPS with a valid certificate; HTTP redirects - [ ] curl to the server IP on 2368 from outside is refused - [ ] No page shows a mixed-content warning ## M3 · Transactional email Magic links and invites arrive in a real inbox. ### Steps 1. Set the mail__ environment variables from your SMTP provider and restart mail__transport, mail__options__host, port, auth user and pass, mail__from. 2. Send a staff invite and a member magic link and read both in a real inbox Check the spam folder too; if they land there, fix SPF for the from domain before moving on. ### Done when - [ ] A member magic link arrives - [ ] A staff invite arrives ## M4 · Newsletter delivery Bulk email through Mailgun with DNS that keeps it out of spam. ### Steps 1. Add SPF, DKIM and DMARC records for the sending domain Mailgun shows the exact records. DMARC starts at p=none. 2. Enter the Mailgun API key and domain in Ghost's Email newsletter settings 3. Send a test newsletter to a Gmail and an Outlook address ### Done when - [ ] The test newsletter reaches the inbox at a major provider, not spam - [ ] A mail-tester style check passes SPF, DKIM and DMARC ### Watch out - Plain SMTP delivers your password resets and will not deliver your newsletter. These are two systems. ## M5 · Memberships and payments Tiers and gating working in test mode before a single live key exists. ### Steps 1. Connect Stripe in test mode and create tiers 2. Run a test subscription and gate a post ### Done when - [ ] A test-mode subscription completes and the member shows the right tier - [ ] A paid post is gated for a free member and readable for a paid one - [ ] Cancelling in Stripe revokes access ## M6 · Theme The default theme adjusted, not a theme from scratch. ### Steps 1. Adjust the default theme's settings and upload any small CSS changes 2. Confirm it survives a restart ### Done when - [ ] Renders correctly on mobile - [ ] The theme survives docker compose restart ## M7 · Backup, and a restore drill A nightly backup off the box and one restore you actually performed. ### Steps 1. Write backup.sh: mysqldump plus a tar of the content volume, to ./backups, fourteen kept, copied off the box Files: `backup.sh` ```sh docker compose exec -T mysql mysqldump -u root -p$MYSQL_ROOT_PASSWORD ghost > backups/ghost-$(date +%F).sql docker run --rm -v ghost_content:/c -v $PWD/backups:/b alpine tar czf /b/content-$(date +%F).tgz -C /c . rclone copy backups remote:ghost-backups/ ``` 2. Schedule it nightly with cron 3. Restore into a throwaway compose stack and check posts, members and images Do this now, not later. Write the date in the README. ### Done when - [ ] Last night's backup exists off the box - [ ] A restore into a clean stack brings up the site with posts, members and images intact - [ ] The restore date is written in the README ## M8 · Upgrades and handover A repeatable upgrade and the honest trade written down. ### Steps 1. Write update.sh: backup, pull, up -d, verify Files: `update.sh` 2. Write the README Every DNS record and what breaks without it, the restore date, and the trade: you now own upgrades, patching, deliverability and support. Files: `README.md` ### Done when - [ ] One upgrade cycle has been run and the site is up - [ ] The README lists every DNS record ===== OPERATIONS.md ===== # Operations · Ghost Pro ## Backup Nightly mysqldump plus content tar, fourteen kept, copied off the box. ## Restore restore.sh into a clean stack; verified once already, re-verify after major upgrades. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Uptime on the homepage and /ghost/api/admin/site/; watch Mailgun's dashboard for bounces. ## Incident checklist Deliverability drop: check DNS records and Mailgun suppressions. Compromise: rebuild from compose, restore, rotate every credential in .env and Stripe. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Newsletter test lands in the inbox at two major providers - [ ] One restore drill performed and dated - [ ] Stripe live keys enabled only after a test-mode subscription and cancellation succeeded - [ ] update.sh run once successfully ## Launch constraint Do not market omitted Ghost Pro capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. The exact public address. Wrong here means broken links and mixed content. GHOST_URL=https://yourdomain.com # Required · secret. openssl rand -base64 24. MYSQL_ROOT_PASSWORD=long-random # Required · secret. Ghost's database password. MYSQL_PASSWORD=long-random # Required. Ghost transactional mail transport. MAIL_TRANSPORT=SMTP # Required. From your SMTP provider. MAIL_HOST=smtp.postmarkapp.com # Required. From your SMTP provider. MAIL_PORT=587 # Required · secret. SMTP username. MAIL_USER=... # Required · secret. SMTP password. MAIL_PASS=... # Required. From address for transactional mail. MAIL_FROM='Your Site' <hello@yourdomain.com>
# Ghost Pro · indie build Ghost, self-hosted properly: the open-source CMS in Docker with MySQL 8, Caddy for HTTPS, transactional email over SMTP, newsletters through Mailgun (the only bulk provider Ghost supports self-hosted), memberships wired to Stripe in test mode first, and a backup you have actually restored once. You save the subscription and take on upgrades and deliverability. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | CMS | The official ghost Docker image | rule zero: do not write a CMS | | Database | MySQL 8.0 or 8.4 in Docker | the only database Ghost supports in production; MariaDB and SQLite are not | | TLS | Caddy | automatic certificates, one config file | | Newsletters | Mailgun | Ghost's bulk sending is built on Mailgun's API specifically | | Payments | Stripe | Ghost's native memberships integration | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A terminal and a code editor** · free - Why: Every step below is a command you type or a file you edit. - Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. - Verify: You can open a folder and run a command in its terminal - [ ] **Git** · free - Why: History for your code, and the way most hosts deploy. - Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. - Verify: git --version prints a version - [ ] **A VPS with at least 2 GB of RAM** · about $6 to $12 a month - Why: Ghost plus MySQL need it. Ubuntu 24.04 with SSH. - Get it: Hetzner CX22 or a DigitalOcean 2 GB droplet. - [ ] **Docker Engine and Compose on the VPS** · free - Why: Ghost and MySQL run as containers. - Get it: Follow docs.docker.com/engine/install/ubuntu, then add your user to the docker group. - Verify: docker compose version prints a version - [ ] **A domain or subdomain** (optional) · roughly $10 a year, or free on an existing domain - Why: Ghost's url setting must be the exact public HTTPS address. - Get it: Register at Cloudflare Registrar, Porkbun or Namecheap, or use a subdomain of one you already own. You add one DNS record in the deploy phase. - [ ] **SMTP credentials for transactional mail** · free tiers exist - Why: Password resets, staff invites and member sign-in links. Any SMTP provider. - Get it: Postmark, Fastmail or Mailgun's SMTP: host, port, username, password. - [ ] **A Mailgun account, API key and sending domain** · free trial, then usage-based - Why: Newsletters will not send over SMTP. Ghost's bulk email is Mailgun-only for self-hosted installs as of 2026. - Get it: Sign up at mailgun.com, add a sending domain (mail.yourdomain.com), add the DNS records it shows (SPF, DKIM, MX), then Settings > API Keys > create a key. Verify current provider support in Ghost's docs before relying on this. - [ ] **A Stripe account with test-mode keys** (optional) · free; fees per transaction when live - Why: Paid memberships. Test mode first, live as a separate deliberate step. - Get it: dashboard.stripe.com > Developers > API keys (toggle Test mode). Ghost connects via Stripe Connect from its settings. - [ ] **Somewhere off the box for backups** · cents a month - Why: A backup on the server being backed up is not a backup. - Get it: Backblaze B2 or Cloudflare R2 bucket plus rclone configured on the VPS. ## Quick start ```sh docker compose up -d docker compose logs -f ghost ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - A custom theme from scratch and any custom blog engine code. - Third-party analytics. - A CDN and WAF in front; worth adding later. - managed hosting - upgrades - backups - email delivery setup - CDN/security - support - easy scaling If one of those is essential to you, that is the reason to keep paying for Ghost Pro, and the README should say so rather than pretend.
# Build brief · Ghost Pro The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a self-hosted Ghost site to replace Ghost Pro. Build it in phases, in the order below. This is an operations task, not a coding task · each phase ends in a verification you actually run, and Phase 7 is the one people skip and regret. ### Rule zero Do not write a CMS. Deploy the open-source Ghost. Any time spent building blog software here is time not spent on the parts that actually break: email deliverability, backups and upgrades. ### Stack (fixed, do not substitute) - Docker Compose: the official `ghost` image and MySQL 8. Ghost requires MySQL 8.0 or 8.4 in production · MariaDB and SQLite are not supported there, and substituting one is a migration you will discover at the worst moment. - Caddy in front for automatic HTTPS. Ghost binds to localhost only. - Named volumes for Ghost content and the database. ### Phase 1 · Bring it up locally Build: `docker-compose.yml` with both services, health checks, a restart policy, and every secret read from `.env` (never inline). Run it locally first, on `http://localhost`. Done when: the site loads, `/ghost` reaches the admin setup screen, and `docker compose down && docker compose up -d` preserves the database and content. Do not build yet: DNS, TLS, email, Stripe. ### Phase 2 · Domain and TLS Build: the Caddy config for the real domain with automatic certificates, Ghost bound to localhost so only Caddy is exposed, and `url` set to the public HTTPS address in Ghost's config. Getting `url` wrong produces a site that loads but generates broken links and mixed content, and it is not obvious from the homepage. Done when: the public domain serves over HTTPS with a valid certificate, HTTP redirects to HTTPS, `curl` against the server's IP on Ghost's port from another machine is refused, and no page contains a mixed-content warning. ### Phase 3 · Transactional email Build: SMTP credentials in `.env` for Ghost's transactional mail · password resets, staff invites, member signin links. Done when: a member magic link and a staff invite both arrive in a real inbox, not just in the container log. Do not build yet: newsletters. They are a different system, see Phase 4. ### Phase 4 · Newsletter delivery Build: bulk email through Mailgun. This is the constraint that surprises people: Ghost's newsletter sending is built on Mailgun's API specifically, and as of 2026 it remains the only first-class bulk provider for a self-hosted install. Plain SMTP delivers your password resets and will not deliver your newsletter. Configure the Mailgun API key and domain, and set up the DNS records · SPF, DKIM and DMARC · before the first send. Done when: a test newsletter reaches a real subscriber at a major provider, lands in the inbox rather than spam, and a mail-tester style check passes SPF, DKIM and DMARC. Verify the current provider support in Ghost's docs before building this · it is the item most likely to have changed. ### Phase 5 · Memberships and payments Build: Stripe keys in `.env`, test mode first. Configure tiers, verify the signup and checkout flow end to end, then switch to live keys as a separate deliberate step. Done when: a test-mode subscription completes, the member appears with the right tier, a paid post is gated for a free member and readable for a paid one, and cancelling in Stripe revokes access. ### Phase 6 · Theme Build: start from the default theme and adjust. Do not write one from scratch in this project. Done when: the site renders correctly on mobile, and the theme survives a Ghost restart without a re-upload. ### Phase 7 · Backup, and a restore drill Build: a nightly cron that runs `mysqldump` and tars the content volume into `./backups/`, keeping 14 days, copied off the machine · a backup that only exists on the server being backed up is not a backup. Then do the part that matters: restore into a fresh throwaway container and confirm the site comes up with the posts, members and images intact. Done when: you have actually completed one restore into a clean environment and the restored site is complete. Not "the script runs" · a restore you have performed once, with the date written in the README. ### Phase 8 · Upgrades and handover Build: an `update.sh` that pulls new images and restarts, with a note to read the release notes before major-version jumps and to take a backup first. Then the README. Done when: one upgrade cycle has been run and the site is still up. ### Out of scope (and why) - A custom theme from scratch, and any custom blog engine code. - Third-party analytics. - A CDN and WAF in front. Worth adding later; not what makes this work on day one. ### README must contain - Every DNS record, including the email ones, with what breaks if each is missing. - The date of the last successful restore drill. - The honest trade, stated plainly: you are saving the subscription and taking on upgrades, patching, deliverability and being your own support. That is the deal.
# Agent instructions · Ghost Pro indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: The official ghost Docker image, MySQL 8.0 or 8.4 in Docker, Caddy, Mailgun, Stripe. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - MySQL 8.0 or 8.4 only. Substituting MariaDB or SQLite is a migration you discover at the worst moment. - Plain SMTP delivers your password resets and will not deliver your newsletter. These are two systems.
# Build plan · Ghost Pro Ghost, self-hosted properly: the open-source CMS in Docker with MySQL 8, Caddy for HTTPS, transactional email over SMTP, newsletters through Mailgun (the only bulk provider Ghost supports self-hosted), memberships wired to Stripe in test mode first, and a backup you have actually restored once. You save the subscription and take on upgrades and deliverability. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Bring it up locally Ghost and MySQL running from one compose file, data surviving a restart. ### Steps 1. Write docker-compose.yml with ghost and mysql:8 Named volumes for content and the database, health checks, restart: unless-stopped, every secret from .env. Files: `docker-compose.yml`, `.env` 2. Start it and reach the setup screen ```sh docker compose up -d docker compose logs -f ghost ``` ### Done when - [ ] The site loads on localhost - [ ] /ghost reaches the admin setup screen - [ ] docker compose down && docker compose up -d preserves posts and settings ### Watch out - MySQL 8.0 or 8.4 only. Substituting MariaDB or SQLite is a migration you discover at the worst moment. ## Phase 2 · Domain and TLS Public HTTPS, Ghost bound to localhost, url set correctly. ### Steps 1. Write the Caddyfile and set GHOST_URL Your domain with reverse_proxy localhost:2368. Ghost's port published on 127.0.0.1 only. Files: `Caddyfile` 2. Point DNS and verify from another machine ### Done when - [ ] HTTPS with a valid certificate; HTTP redirects - [ ] curl to the server IP on 2368 from outside is refused - [ ] No page shows a mixed-content warning ## Phase 3 · Transactional email Magic links and invites arrive in a real inbox. ### Steps 1. Set the mail__ environment variables from your SMTP provider and restart mail__transport, mail__options__host, port, auth user and pass, mail__from. 2. Send a staff invite and a member magic link and read both in a real inbox Check the spam folder too; if they land there, fix SPF for the from domain before moving on. ### Done when - [ ] A member magic link arrives - [ ] A staff invite arrives ## Phase 4 · Newsletter delivery Bulk email through Mailgun with DNS that keeps it out of spam. ### Steps 1. Add SPF, DKIM and DMARC records for the sending domain Mailgun shows the exact records. DMARC starts at p=none. 2. Enter the Mailgun API key and domain in Ghost's Email newsletter settings 3. Send a test newsletter to a Gmail and an Outlook address ### Done when - [ ] The test newsletter reaches the inbox at a major provider, not spam - [ ] A mail-tester style check passes SPF, DKIM and DMARC ### Watch out - Plain SMTP delivers your password resets and will not deliver your newsletter. These are two systems. ## Phase 5 · Memberships and payments Tiers and gating working in test mode before a single live key exists. ### Steps 1. Connect Stripe in test mode and create tiers 2. Run a test subscription and gate a post ### Done when - [ ] A test-mode subscription completes and the member shows the right tier - [ ] A paid post is gated for a free member and readable for a paid one - [ ] Cancelling in Stripe revokes access ## Phase 6 · Theme The default theme adjusted, not a theme from scratch. ### Steps 1. Adjust the default theme's settings and upload any small CSS changes 2. Confirm it survives a restart ### Done when - [ ] Renders correctly on mobile - [ ] The theme survives docker compose restart ## Phase 7 · Backup, and a restore drill A nightly backup off the box and one restore you actually performed. ### Steps 1. Write backup.sh: mysqldump plus a tar of the content volume, to ./backups, fourteen kept, copied off the box Files: `backup.sh` ```sh docker compose exec -T mysql mysqldump -u root -p$MYSQL_ROOT_PASSWORD ghost > backups/ghost-$(date +%F).sql docker run --rm -v ghost_content:/c -v $PWD/backups:/b alpine tar czf /b/content-$(date +%F).tgz -C /c . rclone copy backups remote:ghost-backups/ ``` 2. Schedule it nightly with cron 3. Restore into a throwaway compose stack and check posts, members and images Do this now, not later. Write the date in the README. ### Done when - [ ] Last night's backup exists off the box - [ ] A restore into a clean stack brings up the site with posts, members and images intact - [ ] The restore date is written in the README ## Phase 8 · Upgrades and handover A repeatable upgrade and the honest trade written down. ### Steps 1. Write update.sh: backup, pull, up -d, verify Files: `update.sh` 2. Write the README Every DNS record and what breaks without it, the restore date, and the trade: you now own upgrades, patching, deliverability and support. Files: `README.md` ### Done when - [ ] One upgrade cycle has been run and the site is up - [ ] The README lists every DNS record ## Not in this build - A custom theme from scratch and any custom blog engine code. - Third-party analytics. - A CDN and WAF in front; worth adding later. ## After v1, if you want it - Cloudflare in front for caching and a WAF - A staging stack from last night's backup to test upgrades on
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. The exact public address. Wrong here means broken links and mixed content. GHOST_URL=https://yourdomain.com # Required · secret. openssl rand -base64 24. MYSQL_ROOT_PASSWORD=long-random # Required · secret. Ghost's database password. MYSQL_PASSWORD=long-random # Required. Ghost transactional mail transport. MAIL_TRANSPORT=SMTP # Required. From your SMTP provider. MAIL_HOST=smtp.postmarkapp.com # Required. From your SMTP provider. MAIL_PORT=587 # Required · secret. SMTP username. MAIL_USER=... # Required · secret. SMTP password. MAIL_PASS=... # Required. From address for transactional mail. MAIL_FROM='Your Site' <hello@yourdomain.com>
# Ghost Pro · product brief ## Problem Because Ghost is open source, the core publishing/newsletter/membership product can be self-hosted; the cost is ops, email setup, and maintenance. ## Product outcome A publication platform you operate with confidence: monitored, backed up with a proven restore, upgradable in one command. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - VPS/Docker - domain/DNS - transactional/email provider - backups - theme/configuration ## Explicit non-goals for v1 - A custom theme from scratch and any custom blog engine code. - Third-party analytics. - A CDN and WAF in front; worth adding later. - managed hosting - upgrades - backups - email delivery setup - CDN/security - support - easy scaling ## Success criteria - Newsletter test lands in the inbox at two major providers - One restore drill performed and dated - Stripe live keys enabled only after a test-mode subscription and cancellation succeeded - update.sh run once successfully
# Build brief · Ghost Pro The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a self-hosted Ghost site to replace Ghost Pro. Build it in phases, in the order below. This is an operations task, not a coding task · each phase ends in a verification you actually run, and Phase 7 is the one people skip and regret. ### Rule zero Do not write a CMS. Deploy the open-source Ghost. Any time spent building blog software here is time not spent on the parts that actually break: email deliverability, backups and upgrades. ### Stack (fixed, do not substitute) - Docker Compose: the official `ghost` image and MySQL 8. Ghost requires MySQL 8.0 or 8.4 in production · MariaDB and SQLite are not supported there, and substituting one is a migration you will discover at the worst moment. - Caddy in front for automatic HTTPS. Ghost binds to localhost only. - Named volumes for Ghost content and the database. ### Phase 1 · Bring it up locally Build: `docker-compose.yml` with both services, health checks, a restart policy, and every secret read from `.env` (never inline). Run it locally first, on `http://localhost`. Done when: the site loads, `/ghost` reaches the admin setup screen, and `docker compose down && docker compose up -d` preserves the database and content. Do not build yet: DNS, TLS, email, Stripe. ### Phase 2 · Domain and TLS Build: the Caddy config for the real domain with automatic certificates, Ghost bound to localhost so only Caddy is exposed, and `url` set to the public HTTPS address in Ghost's config. Getting `url` wrong produces a site that loads but generates broken links and mixed content, and it is not obvious from the homepage. Done when: the public domain serves over HTTPS with a valid certificate, HTTP redirects to HTTPS, `curl` against the server's IP on Ghost's port from another machine is refused, and no page contains a mixed-content warning. ### Phase 3 · Transactional email Build: SMTP credentials in `.env` for Ghost's transactional mail · password resets, staff invites, member signin links. Done when: a member magic link and a staff invite both arrive in a real inbox, not just in the container log. Do not build yet: newsletters. They are a different system, see Phase 4. ### Phase 4 · Newsletter delivery Build: bulk email through Mailgun. This is the constraint that surprises people: Ghost's newsletter sending is built on Mailgun's API specifically, and as of 2026 it remains the only first-class bulk provider for a self-hosted install. Plain SMTP delivers your password resets and will not deliver your newsletter. Configure the Mailgun API key and domain, and set up the DNS records · SPF, DKIM and DMARC · before the first send. Done when: a test newsletter reaches a real subscriber at a major provider, lands in the inbox rather than spam, and a mail-tester style check passes SPF, DKIM and DMARC. Verify the current provider support in Ghost's docs before building this · it is the item most likely to have changed. ### Phase 5 · Memberships and payments Build: Stripe keys in `.env`, test mode first. Configure tiers, verify the signup and checkout flow end to end, then switch to live keys as a separate deliberate step. Done when: a test-mode subscription completes, the member appears with the right tier, a paid post is gated for a free member and readable for a paid one, and cancelling in Stripe revokes access. ### Phase 6 · Theme Build: start from the default theme and adjust. Do not write one from scratch in this project. Done when: the site renders correctly on mobile, and the theme survives a Ghost restart without a re-upload. ### Phase 7 · Backup, and a restore drill Build: a nightly cron that runs `mysqldump` and tars the content volume into `./backups/`, keeping 14 days, copied off the machine · a backup that only exists on the server being backed up is not a backup. Then do the part that matters: restore into a fresh throwaway container and confirm the site comes up with the posts, members and images intact. Done when: you have actually completed one restore into a clean environment and the restored site is complete. Not "the script runs" · a restore you have performed once, with the date written in the README. ### Phase 8 · Upgrades and handover Build: an `update.sh` that pulls new images and restarts, with a note to read the release notes before major-version jumps and to take a backup first. Then the README. Done when: one upgrade cycle has been run and the site is still up. ### Out of scope (and why) - A custom theme from scratch, and any custom blog engine code. - Third-party analytics. - A CDN and WAF in front. Worth adding later; not what makes this work on day one. ### README must contain - Every DNS record, including the email ones, with what breaks if each is missing. - The date of the last successful restore drill. - The honest trade, stated plainly: you are saving the subscription and taking on upgrades, patching, deliverability and being your own support. That is the deal.
# Architecture · Ghost Pro ## Stack | Part | Choice | Why | | --- | --- | --- | | CMS | The official ghost Docker image | rule zero: do not write a CMS | | Database | MySQL 8.0 or 8.4 in Docker | the only database Ghost supports in production; MariaDB and SQLite are not | | TLS | Caddy | automatic certificates, one config file | | Newsletters | Mailgun | Ghost's bulk sending is built on Mailgun's API specifically | | Payments | Stripe | Ghost's native memberships integration | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Ghost | content, members, newsletters | It is the product; you do not fork it | | MySQL | all state | Managed MySQL 8 later, same dump format | | Caddy | TLS and the public edge | Any reverse proxy or a CDN in front | | Mail | SMTP for transactional, Mailgun for bulk | SMTP provider freely; bulk provider only as Ghost adds support | | Backup | dump, tar, off-box copy, restore.sh | Any storage rclone speaks | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `GHOST_URL` · required · The exact public address. Wrong here means broken links and mixed content. - `MYSQL_ROOT_PASSWORD` · required, secret · openssl rand -base64 24. - `MYSQL_PASSWORD` · required, secret · Ghost's database password. - `MAIL_TRANSPORT` · required · Ghost transactional mail transport. - `MAIL_HOST` · required · From your SMTP provider. - `MAIL_PORT` · required · From your SMTP provider. - `MAIL_USER` · required, secret · SMTP username. - `MAIL_PASS` · required, secret · SMTP password. - `MAIL_FROM` · required · From address for transactional mail. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Ghost Pro product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: The official ghost Docker image, MySQL 8.0 or 8.4 in Docker, Caddy, Mailgun, Stripe. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - MySQL 8.0 or 8.4 only. Substituting MariaDB or SQLite is a migration you discover at the worst moment. - Plain SMTP delivers your password resets and will not deliver your newsletter. These are two systems.
# Delivery milestones · Ghost Pro Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Bring it up locally Ghost and MySQL running from one compose file, data surviving a restart. ### Steps 1. Write docker-compose.yml with ghost and mysql:8 Named volumes for content and the database, health checks, restart: unless-stopped, every secret from .env. Files: `docker-compose.yml`, `.env` 2. Start it and reach the setup screen ```sh docker compose up -d docker compose logs -f ghost ``` ### Done when - [ ] The site loads on localhost - [ ] /ghost reaches the admin setup screen - [ ] docker compose down && docker compose up -d preserves posts and settings ### Watch out - MySQL 8.0 or 8.4 only. Substituting MariaDB or SQLite is a migration you discover at the worst moment. ## M2 · Domain and TLS Public HTTPS, Ghost bound to localhost, url set correctly. ### Steps 1. Write the Caddyfile and set GHOST_URL Your domain with reverse_proxy localhost:2368. Ghost's port published on 127.0.0.1 only. Files: `Caddyfile` 2. Point DNS and verify from another machine ### Done when - [ ] HTTPS with a valid certificate; HTTP redirects - [ ] curl to the server IP on 2368 from outside is refused - [ ] No page shows a mixed-content warning ## M3 · Transactional email Magic links and invites arrive in a real inbox. ### Steps 1. Set the mail__ environment variables from your SMTP provider and restart mail__transport, mail__options__host, port, auth user and pass, mail__from. 2. Send a staff invite and a member magic link and read both in a real inbox Check the spam folder too; if they land there, fix SPF for the from domain before moving on. ### Done when - [ ] A member magic link arrives - [ ] A staff invite arrives ## M4 · Newsletter delivery Bulk email through Mailgun with DNS that keeps it out of spam. ### Steps 1. Add SPF, DKIM and DMARC records for the sending domain Mailgun shows the exact records. DMARC starts at p=none. 2. Enter the Mailgun API key and domain in Ghost's Email newsletter settings 3. Send a test newsletter to a Gmail and an Outlook address ### Done when - [ ] The test newsletter reaches the inbox at a major provider, not spam - [ ] A mail-tester style check passes SPF, DKIM and DMARC ### Watch out - Plain SMTP delivers your password resets and will not deliver your newsletter. These are two systems. ## M5 · Memberships and payments Tiers and gating working in test mode before a single live key exists. ### Steps 1. Connect Stripe in test mode and create tiers 2. Run a test subscription and gate a post ### Done when - [ ] A test-mode subscription completes and the member shows the right tier - [ ] A paid post is gated for a free member and readable for a paid one - [ ] Cancelling in Stripe revokes access ## M6 · Theme The default theme adjusted, not a theme from scratch. ### Steps 1. Adjust the default theme's settings and upload any small CSS changes 2. Confirm it survives a restart ### Done when - [ ] Renders correctly on mobile - [ ] The theme survives docker compose restart ## M7 · Backup, and a restore drill A nightly backup off the box and one restore you actually performed. ### Steps 1. Write backup.sh: mysqldump plus a tar of the content volume, to ./backups, fourteen kept, copied off the box Files: `backup.sh` ```sh docker compose exec -T mysql mysqldump -u root -p$MYSQL_ROOT_PASSWORD ghost > backups/ghost-$(date +%F).sql docker run --rm -v ghost_content:/c -v $PWD/backups:/b alpine tar czf /b/content-$(date +%F).tgz -C /c . rclone copy backups remote:ghost-backups/ ``` 2. Schedule it nightly with cron 3. Restore into a throwaway compose stack and check posts, members and images Do this now, not later. Write the date in the README. ### Done when - [ ] Last night's backup exists off the box - [ ] A restore into a clean stack brings up the site with posts, members and images intact - [ ] The restore date is written in the README ## M8 · Upgrades and handover A repeatable upgrade and the honest trade written down. ### Steps 1. Write update.sh: backup, pull, up -d, verify Files: `update.sh` 2. Write the README Every DNS record and what breaks without it, the restore date, and the trade: you now own upgrades, patching, deliverability and support. Files: `README.md` ### Done when - [ ] One upgrade cycle has been run and the site is up - [ ] The README lists every DNS record
# Operations · Ghost Pro ## Backup Nightly mysqldump plus content tar, fourteen kept, copied off the box. ## Restore restore.sh into a clean stack; verified once already, re-verify after major upgrades. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Uptime on the homepage and /ghost/api/admin/site/; watch Mailgun's dashboard for bounces. ## Incident checklist Deliverability drop: check DNS records and Mailgun suppressions. Compromise: rebuild from compose, restore, rotate every credential in .env and Stripe. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Newsletter test lands in the inbox at two major providers - [ ] One restore drill performed and dated - [ ] Stripe live keys enabled only after a test-mode subscription and cancellation succeeded - [ ] update.sh run once successfully ## Launch constraint Do not market omitted Ghost Pro capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. The exact public address. Wrong here means broken links and mixed content. GHOST_URL=https://yourdomain.com # Required · secret. openssl rand -base64 24. MYSQL_ROOT_PASSWORD=long-random # Required · secret. Ghost's database password. MYSQL_PASSWORD=long-random # Required. Ghost transactional mail transport. MAIL_TRANSPORT=SMTP # Required. From your SMTP provider. MAIL_HOST=smtp.postmarkapp.com # Required. From your SMTP provider. MAIL_PORT=587 # Required · secret. SMTP username. MAIL_USER=... # Required · secret. SMTP password. MAIL_PASS=... # Required. From address for transactional mail. MAIL_FROM='Your Site' <hello@yourdomain.com>
$ choose a build depth, inspect the files, then open the complete pack in your agent
They pay because publishing should not turn into server administration.
xmanaged hosting
xupgrades
xbackups
xemail delivery setup
xCDN/security
xsupport
xeasy scaling
Don't feel like building it? These folks already made it free.
no votes, no pay-to-list · just what's real
Ghost Pro pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| starter | — | $18/workspace | 1 staff user; 1,000 members; 5 MB/file; 1 newsletter; unlimited email sends. |
| publisher | — | $29/workspace | 3 staff users; 1,000 members; 100 MB/file; 3 newsletters; 3 premium tiers; 15 offers; unlimited email sends. |
| business | — | $199/workspace | 15 staff users; 10,000 members; 250 MB/file; 10 newsletters; 10 premium tiers; 50 offers; unlimited email sends. |
| custom | custom | — | Unlimited staff and members; 1 GB/file; 99.9% uptime SLA. |
free tierno permanent hosted free tier verified; a try-for-free flow is offered, but the public page does not state a numeric trial length
billingmonthly + annual; live accessible state exposes annual rates; upgrade, downgrade or cancel anytime
hidden costsmember growth can force a higher plan; the free .link domain is free for 1 year then renews at the normal rate; Stripe processing still applies; Business custom SSL and managed subdirectory installs are each $50/month
verified 2026-08-13 · source ↗
Vibecode Ghost Pro
Yes. A competent AI coding agent (Claude Code, Codex, Cursor) can build a usable personal Ghost Pro replacement in one session with the prompt on this page. It runs on your own machine or server with no subscription.
How much does Ghost Pro cost?
Ghost Pro costs about $29/month (Publisher, checked 2026-08-13), which is $348 per year. That's what you save by replacing it with one prompt.
What do I lose by replacing Ghost Pro?
Honestly: managed hosting; upgrades; backups; email delivery setup; CDN/security; support; easy scaling. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Ghost Pro?
Yes: Ghost (Ghost Pro without Ghost doing the server, database, mail plumbing, backups, or late-night updates for you.) The prompt is for when you want it exactly your way.