Docker Compose Node.js+MySQL in 2024
docker-compose.yml
version: '3.8'
services:
mysql:
image: mysql:8.3.0
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_ROOT_HOST: "%"
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
volumes:
- mysql:/var/lib/mysql
node:
image: node:20.11.0-alpine
user: ${NODE_USER}
working_dir: /home/node/app
environment:
- NODE_ENV=${NODE_ENV}
command: "yarn start"
ports:
- ${NODE_PORT}:${NODE_PORT}
volumes:
- ./:/home/node/app
depends_on:
- mysql
volumes:
mysql:
This docker compose is designed of local development only.
Networks
You may want to specify networks.
version: '3.8'
services:
mysql:
…
networks:
- nodejs-mysql
node:
…
networks:
- nodejs-mysql
networks:
nodejs-mysql:
driver: bridge
Node Command
I prefer yarn start.
I don’t know why but yarn start command stops container much faster than npm run start.
With npm run start it takes roughly 10s to stop the container. With yarn start it takes less than 1s.
Two commands do not make a difference when create or start a container.
Environment
Replace ${…} with strings if you don’t want to rely on .env.
Create Test App
The rest of article is about creating an example application to see if this docker compose actually works in more realistic development environment.
You need three additional files:
.
├── .env
├── package.json
└── server.js
Run the next command to create them:
touch .env package.json server.js
Add the following codes to each file:
.env
# Docker Node.js
NODE_USER=node
NODE_ENV=production
NODE_PORT=3000
# Docker MySQL
MYSQL_HOST=mysql
MYSQL_PORT=3306
MYSQL_ROOT_PASSWORD=example
MYSQL_DATABASE=example
MYSQL_USER=user
MYSQL_PASSWORD=example
package.json
{
"scripts": {
"start": "nodemon -L server.js"
},
"dependencies": {
"body-parser": "^1.20.2",
"dotenv": "^16.4.1",
"express": "^4.18.2",
"mysql2": "^3.9.1"
},
"devDependencies": {
"nodemon": "^3.0.3"
}
}
You need to pass -L flag when you run Nodemon via container. See Application isn't restarting.
server.js
require('dotenv').config()
const express = require('express')
const bodyParser = require("body-parser")
const mysql = require('mysql2')
const { MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE, MYSQL_PORT, NODE_PORT } = process.env
const uri = `mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@${MYSQL_HOST}:${MYSQL_PORT}/${MYSQL_DATABASE}`
const app = express()
const connection = mysql.createConnection(uri)
app.use(bodyParser.json())
app.get('/', function (req, res) {
res.send('Hello, world')
})
app.get('/books', async function(req, res) {
const sql = 'SELECT * FROM books'
const [books] = await connection.promise().query(sql)
res.json({ data: { books } })
})
app.post('/books', async function(req, res) {
const { title } = req.body
const sql = `INSERT INTO books (title) VALUES ("${title}")`
const [result] = await connection.promise().query(sql)
res.json({ data: { result } })
})
app.listen(NODE_PORT)
Install Node Modules
npm install
# or
yarn
I am not sure if pnpm would work with Docker’s volume mounting.
Compose
# Up
docker compose up --build -d
# Start/stop containers
docker compose start
docker compose stop
# Stop and remove containers, networks
docker compose down
Create MySQL Table
Login in to the MySQL container with cli with the password you define in .env:
docker exec -it your-container-name mysql -u user -p
Run the following SQLs:
use example;
CREATE TABLE books (id int NOT NULL AUTO_INCREMENT, title varchar(255) NOT NULL, PRIMARY KEY (id));
-- Check if the table is created
SHOW TABLES;
Testing
Open http://localhost:3000. You see “Hello, world” message.
Next, open http://localhost:3000/books. This time you see JSON string like this:
{"data":{"books":[]}}
Now open your terminal and make a couple of curl requests:
curl -X POST -H "Content-Type:application/json" -d '{"title": "Atomic Habits"}' http://localhost:3000/books
curl -X POST -H "Content-Type:application/json" -d '{"title": "The Psychology of Money"}' http://localhost:3000/books
You will get a response(output) like below for each request:
{"data":{"result":{"fieldCount":0,"affectedRows":1,"insertId":1,"info":"","serverStatus":2,"warningStatus":0,"changedRows":0}}}
Back to http://localhost:3000/books, you get an array of two books:
{"data":{"books":[{"id":1,"title":"Atomic Habits"},{"id":2,"title":"The Psychology of Money"}]}}
Logs
When you run a container in daemon, you cannot see the log of Node.js process when any error or bug occurs. You have two options to check the log.
1. Docker Dashboard
- Open Docker Dashboard
- Click “Containers”
- Click your node container
- You can see the logs
2. VS Code Docker extension
- Install “Docker” extension if not installed, then restart VS Code
- Open “Command Palette” (Shift + Cmd + p)
- Select “Docker Containers: View Logs”
- Choose your stack
- Choose your node container
- VS Code shows the container logs in Terminal