2023-04-26 15:25:09 +01:00
|
|
|
---
|
|
|
|
categories:
|
|
|
|
- DevOps
|
|
|
|
- Databases
|
|
|
|
tags: [docker, SQL, node-js]
|
|
|
|
---
|
|
|
|
|
|
|
|
# Docker example: NodeJS backend with MySQL database
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
We will utilise [Docker Compose](/DevOps/Docker/Docker_Compose.md) to combine
|
|
|
|
two containers:
|
2023-04-26 15:25:09 +01:00
|
|
|
|
|
|
|
- A container for the NodeJS backend
|
|
|
|
- A container for the MySQL database
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
We will only create a Dockerfile for the NodeJS part since the existing `mysql`
|
|
|
|
image is sufficient for most needs and does not require a specific
|
|
|
|
configuration.
|
2023-04-26 15:25:09 +01:00
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
Each of the files listed below would be saved to the same source directory which
|
|
|
|
would then form the basis of the
|
|
|
|
[build context](/DevOps/Docker/Creating_a_Docker_image.md#creating-a-docker-image).
|
2023-04-26 15:25:09 +01:00
|
|
|
|
|
|
|
## Docker Compose file
|
|
|
|
|
|
|
|
```yml
|
|
|
|
# docker-compose.yml
|
|
|
|
version: "3.8"
|
|
|
|
services:
|
|
|
|
db:
|
|
|
|
image: mysql:8.0
|
|
|
|
container_name: mysql_container
|
|
|
|
environment:
|
|
|
|
MYSQL_ROOT_PASSWORD: your_root_password
|
|
|
|
MYSQL_DATABASE: your_database_name
|
|
|
|
MYSQL_USER: your_database_user
|
|
|
|
MYSQL_PASSWORD: your_database_password
|
|
|
|
volumes:
|
|
|
|
- mysql-data:/var/lib/mysql
|
|
|
|
ports:
|
|
|
|
- "3306:3306"
|
|
|
|
app:
|
|
|
|
build: .
|
|
|
|
container_name: node_app
|
|
|
|
volumes:
|
|
|
|
- .:/usr/src/app
|
|
|
|
environment:
|
|
|
|
MYSQL_HOST: db
|
|
|
|
MYSQL_USER: your_database_user
|
|
|
|
MYSQL_PASSWORD: your_database_password
|
|
|
|
MYSQL_DB: your_database_name
|
|
|
|
depends_on:
|
|
|
|
- db
|
|
|
|
ports:
|
|
|
|
- "3000:3000"
|
|
|
|
|
|
|
|
volumes:
|
|
|
|
mysql-data:
|
|
|
|
```
|
|
|
|
|
|
|
|
## Dockerfile for the NodeJS backend
|
|
|
|
|
|
|
|
```Dockerfile
|
|
|
|
# Dockerfile
|
|
|
|
FROM node:14
|
|
|
|
|
|
|
|
WORKDIR /usr/src/app
|
|
|
|
|
|
|
|
COPY package*.json ./
|
|
|
|
|
|
|
|
RUN npm install
|
|
|
|
|
|
|
|
COPY . .
|
|
|
|
|
|
|
|
EXPOSE 3000
|
|
|
|
|
|
|
|
CMD [ "node", "app.js" ]
|
|
|
|
```
|
|
|
|
|
|
|
|
## NodeJS project setup
|
|
|
|
|
|
|
|
```json
|
|
|
|
// package.json
|
|
|
|
{
|
|
|
|
"name": "node-mysql-docker",
|
|
|
|
"version": "1.0.0",
|
|
|
|
"description": "Node.js and MySQL with Docker",
|
|
|
|
"main": "app.js",
|
|
|
|
"scripts": {
|
|
|
|
"start": "node app.js"
|
|
|
|
},
|
|
|
|
"dependencies": {
|
|
|
|
"express": "^4.17.1",
|
|
|
|
"mysql2": "^2.3.2"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
```js
|
|
|
|
// app.js
|
|
|
|
const express = require("express");
|
|
|
|
const mysql = require("mysql2/promise");
|
|
|
|
|
|
|
|
const app = express();
|
|
|
|
|
|
|
|
const { MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DB } = process.env;
|
|
|
|
|
|
|
|
const createConnection = async () => {
|
|
|
|
return await mysql.createConnection({
|
|
|
|
host: MYSQL_HOST,
|
|
|
|
user: MYSQL_USER,
|
|
|
|
password: MYSQL_PASSWORD,
|
|
|
|
database: MYSQL_DB,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
app.get("/", async (req, res) => {
|
|
|
|
const connection = await createConnection();
|
|
|
|
const [rows] = await connection.query("SELECT 1 + 1 AS solution");
|
|
|
|
res.send(`Hello World! The solution is ${rows[0].solution}`);
|
|
|
|
});
|
|
|
|
|
|
|
|
const PORT = 3000;
|
|
|
|
app.listen(PORT, () => {
|
|
|
|
console.log(`Server is running on port ${PORT}`);
|
|
|
|
});
|
|
|
|
```
|
|
|
|
|
|
|
|
To start up the environment you would then run:
|
|
|
|
|
|
|
|
```
|
|
|
|
docker-compose -up
|
|
|
|
```
|
|
|
|
|
|
|
|
## Environments
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
In the example, the database connection information in the Node source is coming
|
|
|
|
from the
|
|
|
|
[`process.env`](/Programming_Languages/NodeJS/Architecture/Managing_environments.md)
|
|
|
|
object, which itself is sourcing the values `MYSQL_HOST`, `MYSQL_PASSWORD` etc
|
|
|
|
from the Docker compose file. Therefore these values are hardcoded there.
|
2023-04-26 15:25:09 +01:00
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
This is not good practice as it exposes sensitive information and make managing
|
|
|
|
different deployment environments (development, stage, test etc.) difficult.
|
2023-04-26 15:25:09 +01:00
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
To get around this we would create an `.env` file in the project directory that
|
|
|
|
is Git ignored:
|
2023-04-26 15:25:09 +01:00
|
|
|
|
|
|
|
```sh
|
|
|
|
# .env
|
|
|
|
MYSQL_ROOT_PASSWORD=your_root_password
|
|
|
|
MYSQL_DATABASE=your_database_name
|
|
|
|
MYSQL_USER=your_database_user
|
|
|
|
MYSQL_PASSWORD=your_database_password
|
|
|
|
```
|
|
|
|
|
|
|
|
Then the `docker-compose.yml` file can be updated to use these variables:
|
|
|
|
|
|
|
|
```yml
|
|
|
|
version: "3.8"
|
|
|
|
services:
|
|
|
|
db:
|
|
|
|
image: mysql:8.0
|
|
|
|
container_name: mysql_container
|
|
|
|
environment:
|
|
|
|
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
|
|
|
|
MYSQL_DATABASE: ${MYSQL_DATABASE}
|
|
|
|
MYSQL_USER: ${MYSQL_USER}
|
|
|
|
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
|
|
|
|
volumes:
|
|
|
|
- mysql-data:/var/lib/mysql
|
|
|
|
ports:
|
|
|
|
- "3306:3306"
|
|
|
|
app:
|
|
|
|
build: .
|
|
|
|
container_name: node_app
|
|
|
|
volumes:
|
|
|
|
- .:/usr/src/app
|
|
|
|
environment:
|
|
|
|
MYSQL_HOST: db
|
|
|
|
MYSQL_USER: ${MYSQL_USER}
|
|
|
|
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
|
|
|
|
MYSQL_DB: ${MYSQL_DATABASE}
|
|
|
|
depends_on:
|
|
|
|
- db
|
|
|
|
ports:
|
|
|
|
- "3000:3000"
|
|
|
|
|
|
|
|
volumes:
|
|
|
|
mysql-data:
|
|
|
|
```
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
`${VARIABLE_NAME}` syntax is used to reference environment variables from the
|
|
|
|
.env file in the `docker-compose.yml` file. Docker Compose will automatically
|
|
|
|
load the variables from the .env file when starting the services.
|
2023-04-26 15:25:09 +01:00
|
|
|
|
|
|
|
### Development, staging, production environments
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
To specify different connection details for different environments you would
|
|
|
|
create different `.env` files for each:
|
2023-04-26 15:25:09 +01:00
|
|
|
|
|
|
|
- `.env.development`
|
|
|
|
- `.env.staging`
|
|
|
|
- `.env.production`
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
Each file will contain **environment-specific variables**, such as database
|
|
|
|
credentials, API keys, and other configuration details.
|
2023-04-26 15:25:09 +01:00
|
|
|
|
|
|
|
For example, development and production:
|
|
|
|
|
|
|
|
```yml
|
|
|
|
# docker-compose.development.yml
|
|
|
|
version: '3.8'
|
|
|
|
services:
|
|
|
|
db:
|
|
|
|
...
|
|
|
|
app:
|
|
|
|
...
|
|
|
|
environment:
|
|
|
|
MYSQL_HOST: db
|
|
|
|
MYSQL_USER: ${MYSQL_USER}
|
|
|
|
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
|
|
|
|
MYSQL_DB: ${MYSQL_DATABASE}
|
|
|
|
NODE_ENV: development
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
```yml
|
|
|
|
# docker-compose.production.yml
|
|
|
|
version: '3.8'
|
|
|
|
services:
|
|
|
|
db:
|
|
|
|
...
|
|
|
|
app:
|
|
|
|
...
|
|
|
|
environment:
|
|
|
|
MYSQL_HOST: db
|
|
|
|
MYSQL_USER: ${MYSQL_USER}
|
|
|
|
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
|
|
|
|
MYSQL_DB: ${MYSQL_DATABASE}
|
|
|
|
NODE_ENV: production
|
|
|
|
`
|
|
|
|
```
|
|
|
|
|
|
|
|
Then you would select the specific environment with your run command:
|
|
|
|
|
|
|
|
```
|
|
|
|
docker-compose -f docker-compose.development.yml up -d
|
|
|
|
```
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
Docker won't know by default which `.env` file to use from that command however.
|
|
|
|
Assuming all the files are in the same directory you can use Bash substitution
|
|
|
|
to specify the source of the environment specific variables:
|
2023-04-26 15:25:09 +01:00
|
|
|
|
|
|
|
```sh
|
|
|
|
export $(cat .env.development | xargs) && docker-compose -f docker-compose.development.yml up -d
|
|
|
|
```
|