Using Docker Compose & Mysql to set up a ghost blog

This is a post that I wanted to write long time ago, but I finally had time to. At this point, if you look closely, it should come as no surprise that my blog is powered by Ghost, it's also deployed inside a Docker container.

Ghost is a widely used blogging platform written entirely in javascript, both on the client and on the server using node and expressjs. By default, it uses a SQLite instance for persistence, but I've never really liked SQLite that much, and also to fit an even better use-case for using Docker, while maintaining separation of concerns. So we're splitting up the containers by responsibility and using MySQL as our "favorite database"!

We will split the responsibilities up in Docker containers which runs completely isolated from each other. One for the ghost platform and one for the database.

Using Docker Compose, we can easily manage our Docker containers and their respective builds. If you've never played around with Docker before, I would encourage you to go familiarize your self with it's documentation before jumping in to Docker Compose.
Docker-comp provides a clean interface for managing containers, and lets you handle all your app's services from a single source.

1- Installing Docker & Docker-Compose

Docker is well documented, so all what you've to do is to follow instructions for installing Docker engine and Docker Compose ;)

2- Docker Compose Config: docker-compose.yml

      version: '2'
        ghost:
          image: ghost:latest
          container_name: ghost-blog
          environment:
            - NODE_ENV=production
            - MYSQL_DATABASE={{db-name}} # Change {{db-name}}
            - MYSQL_USER={{username}} # Change {{username}}
            - "MYSQL_PASSWORD={{db-password}}" # Change {{db-password}}
            - "MAILGUN_USER={{mailgun-user}}" # Change {{mailgun-user}}
            - "MAILGUN_PASSWORD={{mailgun-password}}" # Change {{mailgun-password}}
          volumes:
            - ./ghost:/var/lib/ghost
          ports:
            - 2368:2368
          depends_on:
            - mysql
          restart: always

        mysql:
          image: mysql:latest
          container_name: ghost-db
          environment:
            - MYSQL_DATABASE={{db-name}} # Change {{db-name}}
            - MYSQL_ROOT_PASSWORD={{root-password}} # Change {{root-password}}
            - MYSQL_USER={{username}} # Change {{username}}
            - MYSQL_PASSWORD={{db-password}} # Change {{db-password}}
          volumes:
            - ./db:/var/lib/mysql
          restart: always

Our Compose file takes the following form. Two services are defined, a Mysql service and a Ghost service. The Mysql service is configured via environment variables set in the docker-compose file. We use the official Mysql Docker image that Compose will automatically pull from the Docker hub. The Ghost service using the official Ghost image; it depends on the Mysql service to ensure that the database will start first. We expose the default port of Ghost 2368 to port 2368 of our Docker host. we also use data Volumes for both services.

Ghost Config: config.js

              // # Ghost Configuration
              // Setup your Ghost install for various [environments](http://support.ghost.org/config/#about-environments).

              // Ghost runs in `development` mode by default. Full documentation can be found at http://support.ghost.org/config/

              var path = require('path'),
                  config;

              config = {
                  // ### Production
                  // When running Ghost in the wild, use the production environment.
                  // Configure your URL and mail settings here
                  production: {
                      url: 'https://your-url-here.com',
                      mail: {},
                      database: {
                        atabase: {
                            client: 'mysql',
                            connection: {
                                host     : 'mysql',
                                user     : process.env.MYSQL_USER, // Copy the MYSQL_USER from docker-compose.yml
                                password : process.env.MYSQL_PASSWORD, // Copy the MYSQL_PASSWORD from docker-compose.yml
                                database : process.env.MYSQL_DATABASE, // Copy the MYSQL_DATABASE from docker-compose.yml
                                charset  : 'utf8'
                          }
                        },

                        server: {
                            host: '0.0.0.0',
                            port: '2368'
                        },
                        mail: {
                            transport: 'SMTP',
                            options: {
                                service: 'Mailgun',
                                auth: {
                                    user: process.env.MYSQL_USER,
                                    pass: process.env.MYSQL_USER
                                }
                            }
                        },

                      paths: {
                          contentPath: path.join(process.env.GHOST_CONTENT, '/')
                      },
                  },

                  // ### Development **(default)**
                  development: {
                      // The url to use when providing links to the site, E.g. in RSS and email.
                      // Change this to your Ghost blog's published URL.
                      url: 'http://localhost:2368',

                      // Example refferer policy
                      // Visit https://www.w3.org/TR/referrer-policy/ for instructions
                      // default 'origin-when-cross-origin',
                      // referrerPolicy: 'origin-when-cross-origin',

                      // Example mail config
                      // Visit http://support.ghost.org/mail for instructions
                      // ```
                      //  mail: {
                      //      transport: 'SMTP',
                      //      options: {
                      //          service: 'Mailgun',
                      //          auth: {
                      //              user: '', // mailgun username
                      //              pass: ''  // mailgun password
                      //          }
                      //      }
                      //  },
                      // ```

                      // #### Database
                      // Ghost supports sqlite3 (default), MySQL & PostgreSQL
                      database: {
                          client: 'sqlite3',
                          connection: {
                              filename: path.join(process.env.GHOST_CONTENT, '/data/ghost-dev.db')
                          },
                          debug: false
                      },
                      // #### Server
                      // Can be host & port (default), or socket
                      server: {
                          // Host to be passed to node's `net.Server#listen()`
                          host: '0.0.0.0',
                          // Port to be passed to node's `net.Server#listen()`, for iisnode set this to `process.env.PORT`
                          port: '2368'
                      },
                      // #### Paths
                      // Specify where your content directory lives
                      paths: {
                          contentPath: path.join(process.env.GHOST_CONTENT, '/')
                      }
                  },

                  // **Developers only need to edit below here**

                  // ### Testing
                  // Used when developing Ghost to run tests and check the health of Ghost
                  // Uses a different port number
                  testing: {
                      url: 'http://0.0.0.0:2369',
                      database: {
                          client: 'sqlite3',
                          connection: {
                              filename: path.join(process.env.GHOST_CONTENT, '/data/ghost-test.db')
                          },
                          pool: {
                              afterCreate: function (conn, done) {
                                  conn.run('PRAGMA synchronous=OFF;' +
                                  'PRAGMA journal_mode=MEMORY;' +
                                  'PRAGMA locking_mode=EXCLUSIVE;' +
                                  'BEGIN EXCLUSIVE; COMMIT;', done);
                              }
                          },
                          useNullAsDefault: true
                      },
                      server: {
                          host: '0.0.0.0',
                          port: '2369'
                      },
                      logging: false
                  },

                  // ### Testing MySQL
                  // Used by Travis - Automated testing run through GitHub
                  'testing-mysql': {
                      url: 'http://0.0.0.0:2369',
                      database: {
                          client: 'mysql',
                          connection: {
                              host     : '0.0.0.0',
                              user     : 'root',
                              password : '',
                              database : 'ghost_testing',
                              charset  : 'utf8'
                          }
                      },
                      server: {
                          host: '0.0.0.0',
                          port: '2369'
                      },
                      logging: false
                  },

                  // ### Testing pg
                  // Used by Travis - Automated testing run through GitHub
                  'testing-pg': {
                      url: 'http://0.0.0.0:2369',
                      database: {
                          client: 'pg',
                          connection: {
                              host     : '0.0.0.0',
                              user     : 'postgres',
                              password : '',
                              database : 'ghost_testing',
                              charset  : 'utf8'
                          }
                      },
                      server: {
                          host: '0.0.0.0',
                          port: '2369'
                      },
                      logging: false
                  }
              };

              module.exports = config;

It's a long configuration, I acknowledge, but now everything could be dynamically configured.

We already specified on Docker compose file some env variables: database engine to use and it's parameters, as well as the Node environment to run under (you'd be surprised on how many people run Ghost on development :P).

And voila! Docker Compose is a very handy tool that helps you write a distributed application definition in a single YAML file.