├── .gitignore ├── .prettierrc ├── Procfile ├── README.md ├── bin ├── alter-auth-method ├── cloudinary.sh ├── create-config └── themes.sh ├── index.js ├── package.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: yarn start 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ghost example 2 | 3 | This example deploys self-hosted version of [Ghost](https://ghost.org/). Internally it uses a MySQL database to store the data. 4 | 5 | [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template/ghost) 6 | 7 | ## ✨ Features 8 | 9 | - Ghost 10 | - MySQL 11 | 12 | ## 💁‍♀️ How to use 13 | 14 | - Click the Railway button 👆 15 | - Add the environment variables 16 | - If you do not add the `CLOUDINARY_URL` environment variable, your images/files will not be persisted between deploys. 17 | - Add the `MAILGUN_SMTP_LOGIN` and `MAILGUN_SMTP_PASSWORD` variables if you want to invite users to your admin panel or send emails to your subscribers when you publish a new post. 18 | 19 | ## 📝 Notes 20 | 21 | - Railway's filesystem is ephemeral which is why any changes to the filesystem are not persisted between deploys. This is why, this example uses Cloudinary for storage. 22 | - The above limitation also affects the way themes work with Ghost, we use the `bin/themes.sh` script to copy over the themes every time you deploy. That way, the theme is always present. 23 | - To add a theme, first add the package as a dependency to the `package.json` file and then add it to the list of themes in the `bin/themes.sh` file. 24 | - Do NOT add a theme directly using the Ghost UI, it will look like it worked but will break whenever you deploy your app again. 25 | -------------------------------------------------------------------------------- /bin/alter-auth-method: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | Ghost internally uses the `mysql` library which does not support the `caching_sha2_password` 5 | authentication method and hence does not work properly with MySQL 8. 6 | 7 | So we use this function to tell MySQL to authenticate our user using the 8 | `mysql_native_password` method by using the `mysql2` library which then allows Ghost to use 9 | our database with that same authentication method. 10 | */ 11 | 12 | var mysql = require("mysql2"); 13 | 14 | alterAuthenticationMethod(); 15 | 16 | function alterAuthenticationMethod() { 17 | var connection = mysql.createConnection(process.env.MYSQL_URL); 18 | connection.query( 19 | "ALTER USER `root`@`%` IDENTIFIED WITH mysql_native_password BY ?", 20 | [process.env.MYSQLPASSWORD], 21 | function (err, results, fields) { 22 | if (err) 23 | console.log("There was an error altering the authentication method."); 24 | 25 | connection.end(function (err) { 26 | if (err) console.log("There was an error closing the connection."); 27 | }); 28 | } 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /bin/cloudinary.sh: -------------------------------------------------------------------------------- 1 | mkdir -p ./content/adapters/storage 2 | cp -r ./node_modules/ghost-storage-cloudinary ./content/adapters/storage/cloudinary 3 | -------------------------------------------------------------------------------- /bin/create-config: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | This file creates and writes our production config after the build has been completed. 5 | Make changes here if you want to use a different image storage service or email provider. 6 | */ 7 | 8 | var fs = require("fs"); 9 | var path = require("path"); 10 | 11 | var appRoot = path.join(__dirname, ".."); 12 | var contentPath = path.join(appRoot, "/content/"); 13 | function createConfig() { 14 | var fileStorage, storage; 15 | 16 | if (process.env.CLOUDINARY_URL) { 17 | console.log("CLOUDINARY_URL found, setting storage to cloudinary"); 18 | fileStorage = true; 19 | storage = { 20 | active: "cloudinary", 21 | cloudinary: { 22 | useDatedFolder: false, 23 | upload: { 24 | use_filename: true, 25 | unique_filename: false, 26 | overwrite: false, 27 | folder: "ghost-blog-images", 28 | tags: ["blog"], 29 | }, 30 | fetch: { 31 | quality: "auto", 32 | secure: true, 33 | cdn_subdomain: true, 34 | }, 35 | }, 36 | }; 37 | } else { 38 | console.log("CLOUDINARY_URL not found, setting storage to false"); 39 | fileStorage = false; 40 | storage = {}; 41 | } 42 | 43 | config = { 44 | url: process.env.BLOG_URL, 45 | logging: { 46 | level: "info", 47 | transports: ["stdout"], 48 | }, 49 | mail: { 50 | transport: "SMTP", 51 | options: { 52 | service: "Mailgun", 53 | auth: { 54 | user: process.env.MAILGUN_SMTP_LOGIN, 55 | pass: process.env.MAILGUN_SMTP_PASSWORD, 56 | }, 57 | }, 58 | }, 59 | fileStorage: fileStorage, 60 | storage: storage, 61 | database: { 62 | client: "mysql", 63 | connection: { 64 | host: process.env.MYSQLHOST, 65 | port: process.env.MYSQLPORT, 66 | user: process.env.MYSQLUSER, 67 | password: process.env.MYSQLPASSWORD, 68 | database: process.env.MYSQLDATABASE, 69 | }, 70 | pool: { min: 0, max: 5 }, 71 | debug: false, 72 | }, 73 | server: { 74 | host: "0.0.0.0", 75 | port: process.env.PORT, 76 | }, 77 | paths: { 78 | contentPath: contentPath, 79 | }, 80 | }; 81 | 82 | return config; 83 | } 84 | 85 | var configContents = JSON.stringify(createConfig(), null, 2); 86 | fs.writeFileSync(path.join(appRoot, "config.production.json"), configContents); 87 | fs.mkdirSync(path.join(contentPath, "/data/"), { recursive: true }); 88 | -------------------------------------------------------------------------------- /bin/themes.sh: -------------------------------------------------------------------------------- 1 | themes=( 2 | casper 3 | lyra 4 | ) 5 | 6 | mkdir -p content/themes/ 7 | for theme in "${themes[@]}" 8 | do 9 | cp -Rf "node_modules/$theme" content/themes/$theme 10 | done 11 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var ghost = require("ghost"); 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ghost", 3 | "version": "4.47.4", 4 | "description": "Deploy Ghost v4 on Railway", 5 | "main": "index.js", 6 | "author": "farazpatankar", 7 | "license": "MIT", 8 | "engines": { 9 | "node": "16.X" 10 | }, 11 | "dependencies": { 12 | "casper": "github:TryGhost/Casper#main", 13 | "ghost": "4.47.4", 14 | "ghost-storage-cloudinary": "2.2.1", 15 | "lyra": "github:TryGhost/lyra#main", 16 | "mysql2": "2.3.3" 17 | }, 18 | "scripts": { 19 | "start": "bin/create-config && node index.js", 20 | "postinstall": "bin/alter-auth-method && bash bin/cloudinary.sh && bash bin/themes.sh" 21 | } 22 | } 23 | --------------------------------------------------------------------------------