Introduction
Today, I want to talk about running a real-world application in containers using Docker Compose. Last week, I published a post on how to use a docker-compose.yml file and setting up local environments directly on your computer, "Using Docker Compose to Manage Multi-Container Networks". I also published an article about "Managing Multi-Container Setups with Docker Compose".
Now, let’s discuss how to use Docker Compose for real application examples, not just theoretical talking. Since I use Ghost for my blog and occasionally need to make changes to themes or configurations, it’s helpful to see how these changes look before applying them to the live site. For this purpose, I want to create a local development environment for testing.
Set it up
We’ll set up Docker Compose for Ghost and use it to test various changes locally. First, we’ll run a single container for the Ghost blog itself. To do this, we’ll locate the official Docker container for Ghost (latest version) on Docker Hub. After running it, we’ll notice that the container needs a database connection and additional configuration to function properly.
docker run --name ghost-blog -p 2368:2368 ghost:latest
Open a browser and navigate to http://localhost:2368 to check the blog.
- Explanation:
--name ghost-blog: Names the container for easy identification.-p 2368:2368: Maps port 2368 of the container to port 2368 on your host machine.ghost:latest: Specifies the Ghost image to use.
From the Ghost container log:
[2024-12-05 17:54:17] INFO Ghost is running in production...
[2024-12-05 17:54:17] INFO Your site is now available on http://localhost:2368/
[2024-12-05 17:54:17] INFO Ctrl+C to shut down
[2024-12-05 17:54:17] INFO Ghost server started in 2.146s
[2024-12-05 17:54:18] ERROR connect ECONNREFUSED 127.0.0.1:3306
connect ECONNREFUSED 127.0.0.1:3306
"Unknown database error"
Error ID:
500
Error Code:
ECONNREFUSED
----------------------------------------
Error: connect ECONNREFUSED 127.0.0.1:3306
at /var/lib/ghost/versions/5.103.0/node_modules/knex-migrator/lib/database.js:57:19
at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1555:16)
[2024-12-05 17:54:18] WARN Ghost is shutting down
[2024-12-05 17:54:18] WARN Ghost has shut down
[2024-12-05 17:54:18] WARN Your site is now offline
[2024-12-05 17:54:18] WARN Ghost was running for a few secondsNext, we’ll add a database container since Ghost requires a database to store its content. It supports MySQL, SQLite, and others. We’ll use MySQL for this setup. Looking at the Ghost Docker Hub page. Find the corresponding Docker image of MySQL, and use it for our setup.
Run the following command to start a MySQL container:
docker run --name ghost-db -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=ghost -p 3306:3306 -d mysql:latest
- Explanation:
--name ghost-db: Names the database container.-e MYSQL_ROOT_PASSWORD=root: Sets the root password for MySQL.-e MYSQL_DATABASE=ghost: Creates a database namedghost.-p 3306:3306: Maps port 3306 to the host for database access.-d: Runs the container in detached mode.
Since managing multiple containers manually can be cumbersome, we’ll write a docker-compose.yml file to simplify the setup and configuration process. The file will define two services: one for the Ghost blog and one for the database. For now, we’ll keep it simple without adding volumes or custom networks.
To manage the Ghost blog and the database together, create a docker-compose.yml file in your project directory with the following content:
version: '3.8'
services:
ghost-blog:
image: ghost:latest
ports:
- "2368:2368"
environment:
database__client: mysql
database__connection__host: ghost-db
database__connection__user: root
database__connection__password: root
database__connection__database: ghost
depends_on:
- ghost-db
ghost-db:
image: mysql:latest
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: ghost
ports:
- "3306:3306"
We’ll configure the database container to initialize with the correct settings and tell the Ghost container how to connect to the database. Finally, we’ll test the setup to ensure everything runs as expected.
Run the following command to start both containers:
docker-compose up
- Explanation:
docker-compose up: Starts all services defined in thedocker-compose.ymlfile.
Verify the Setup
- Open your browser and navigate to
http://localhost:2368to access the Ghost blog.
Check the logs for any issues:
docker-compose logs
Preventing Data Loss with Docker Volumes
By default, any data generated or stored by your containers is ephemeral—it disappears when the containers are stopped or removed. This is especially problematic for applications like Ghost and MySQL, where blog content and database data must persist between container restarts. To prevent data loss, we can use Docker volumes to store this data outside of the container's lifecycle.
For our Ghost blog setup, we’ll create volumes for:
- The Ghost blog content (e.g., themes, images, settings).
- The MySQL database data.
Updating the docker-compose.yml File
Modify your docker-compose.yml file to include volumes for both services. Here's the updated configuration:
version: '3.8'
services:
ghost-blog:
image: ghost:latest
ports:
- "2368:2368"
environment:
database__client: mysql
database__connection__host: ghost-db
database__connection__user: root
database__connection__password: root
database__connection__database: ghost
depends_on:
- ghost-db
volumes:
- ghost-content:/var/lib/ghost/content # Persist Ghost blog content
ghost-db:
image: mysql:latest
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: ghost
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql # Persist MySQL database data
volumes:
ghost-content: # Named volume for Ghost content
mysql-data: # Named volume for MySQL data
Explanation:
- Ghost Blog Volume (
ghost-content):- The
/var/lib/ghost/contentdirectory inside the Ghost container stores themes, images, and other content. By mapping this directory to a volume (ghost-content), any changes made to themes or content will be saved persistently.
- The
- MySQL Volume (
mysql-data):- The
/var/lib/mysqldirectory inside the MySQL container holds the database files. Mapping this to a volume (mysql-data) ensures that all blog data stored in the database is preserved.
- The
- Global Volumes Section:
- The
volumessection at the bottom of the file defines the named volumesghost-contentandmysql-data. These volumes are created and managed by Docker.
- The
Customize and Test
- Make changes to the theme or configuration files.
- Test the blog locally to verify your updates before applying them to the live site.
Conclusion
With this setup, you can easily run a local Ghost blog using Docker Compose. This environment provides a safe space to test themes, plugins, and configurations, ensuring that your live blog remains unaffected.
Feel free to extend this setup by adding volumes for data persistence or custom networks for advanced use cases.