---
# System prepended metadata

title: Docker Note
tags: [docker]

---

###### tags: `docker`
# Docker Note
開始教學:
安裝好docker後，打開終端機跑下面這段code
`docker run -d -p 80:80 docker/getting-started`
針對flag做說明:
- `-d`: detached mode將container在背景中執行
- `-p 80:80`: 將主機的80port對照到container的80port，如果你的主機上有其他app運行在80port上你可以改為其他port例如3000:80，運行成功後你可以打開http://localhost:3000來看看結果。
- docker/getting-started: 要使用的image檔案名稱

:::info
你也可以使用簡寫的方式來縮短code，例如:
` docker run -dp 80:80 docker/getting-started`
:::

### The Docker Dashboard
在進到下一階段前，你可以先打開Docker的dashboard，裡面可以讓你清楚的看到有哪些containers在你的主機上運行，也讓你快速查閱logs還有輕鬆地調整container的lifecycle。
打開dashboard你應該會看到，這個課程正在運行中，而名字會是隨機的。

### What is a container?
簡單來說，container就是在你主機上運行的一個程式是與其他程式分別隔離開的，這類的隔離方式是利用了Linux長久以來有的特色[kernel namespaces and cgroups](https://medium.com/@saschagrunert/demystifying-containers-part-i-kernel-space-2c53d6979504)，Docker就是讓此特色功能變得簡單易用。

### Waht is a container image?
當運行一個container時會使用獨立的檔案系統，這獨立的檔案系統是由container image提供的，因為image包含了container的檔案系統，他必定會包含運作一個程式需要的所有檔案(all dependencies, configuration, scripts, binaries, etc.)，image也會包含其他給container的configuration例如環境變數、運行程式的預設指令以及其他的metadata。

## Containerize an application
接下來的課程會使用一個nodejs環境寫的簡易todo list來做示範，可以到github clone[這個專案](https://github.com/docker/getting-started/tree/master)，並將裡面的app資料夾拉出來做練習。

### Build this app's container image
首先建立一個`Dockerfile`的檔案，Dockerfile只有文字沒有副檔名。它包含了用來建立image的步驟指令。
1. 在剛剛的app資料夾內建立一個dockerfile
```shell=
touch Dockerfile
```
2. 並將下面的內容加入到dockerfile內，如果你在之前就寫過dockerfile，你可能會覺得這內容有點缺陷，但別擔心，會續做再多做說明。
```dockerfile=
# syntax=docker/dockerfile:1
FROM node:12-alpine
RUN apk add --no-cache python2 g++ make
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]
EXPOSE 3000
```

3. 打開終端機並進到app資料夾內，現在使用指令`docker build`來建立container image。
```shell=
docker build -t getting-started .
```
你會注意到過程下載了許多"layers"，這是因為我們指示builder要有`node:12-alpine`但因為自身主機上沒有這個image所以會需要先下載。

在下載完畢後會將其複製到程式內並再使用`yarn`來安裝程式的分支，`CMD`這個指令直接設定了一個在當從此image運行container時要運行的預設指令。

`-t`這個標籤會將image下標註，簡單來想就是命名一個人們易讀的image名稱，並可以用這個名稱來運作一個container。

還有一個最後的`.`告訴了`docker build`指令要去尋找當前資料夾的`Dockerfile`。

### Start an app container
現在有了image，就可以來運行我們的程式囉，我們使用`docker run`指令。
1. 藉由`docker run`指令來將剛剛建立的image來運行起來:
```shell=
docker run -dp 3000:3000 getting-started
```
2. 幾秒後，打開你的瀏覽器輸入http://localhost:3000，就能看到app囉。
3. 可以進去嘗試使用看看這個簡易的todo app，看是不是都正常。

接下來打開dashboard，你應該可以看到有兩個container運行在上面。

## Update the application

### Update the source code
1. 在`src/static/js/app.js`第56行替換新的文字
```htmlembedded=
 - <p className="text-center">No items yet! Add one above!</p>
 + <p className="text-center">You have no todo items yet! Add one above!</p>
```
2. 來更新image的版本，使用跟先前一項的指令
```shell=
docker build -t getting-started .
```
3. 啟動一個新的container
```shell=
docker run -dp 3000:3000 getting-started
```
你可能會看到以下的錯誤訊息:
```!
docker: Error response from daemon: driver failed programming external connectivity on endpoint determined_wilson (a14b6b63f2b3d89ba8b3eb0e8301ee0bb59a27bb74740218a45ae58c0f76b0c0): Bind for 0.0.0.0:3000 failed: port is already allocated.
```
怎麼回事呢？ 我們無法使用新的container因為舊的container依舊在運行中使用的是3000 port，且一個特定的port只能有一個特定的程序運行在上面，為解決此，我們可以移除舊的container。

### Replace the old container
要移除container首先要把其暫停(終止)，後在移除，以下有兩種方式可以做到，請自由選擇想要的方式進行。

#### Remove a container using the CLI
1. 拿到container的id
```shell=
docker ps
```
2. 使用`docker stop`來停止container
```shell=
docker stop <container-id>
```
3. 一但暫停後你可以使用`docker rm`來將其移除
```shell=
docker rm <container-id>
```
:::info
你可以同時暫停和移除container藉由加上 `-f`標籤
```shell=
docker rm -f <the-container-id>
```
:::

#### Remove a container using the Docker Dashboard
直接打開dashboard，點擊container右邊的垃圾桶按鈕並確認。


#### Start the updated app container
1. 現在再一次啟動你更新過後的app
```shell=
docker run -dp 3000:3000 getting-started
```
2. 重新整理你的畫面就會看到新的字句被更新上去了

在更新後你可能會注意到下列兩件事:
- 所有在原本存在todo list的資料都消失了，這並不好，後面會繼續說道。
- 為了一個小更新需要經過這麼多步驟，你後續會學習到如何不用每次都重建以及重啟當你需要做檔案更動時。

## Share the applcation
先前已建立了一個image，你需要使用Docker registry來建立，預設的registry是Docker Hub且是所有目前使用過的image的來源處。

### Create a repo
1. 建立並登入[Docker Hub](https://hub.docker.com/)
2. 點擊**Create Repository**
3. repo名稱請使用`getting-started`.確保為公開`Public`
4. 點擊`Create`

建立完畢後會看到頁面中如下圖有一組command，這是可以用來push到此repo的指令:
![](https://i.imgur.com/e4jDi3Q.png)

### Push the image
1. 先試著執行那條指令，請自行替換自己的命名
```shell=
docker push louischen1995/getting-started
The push refers to repository [docker.io/louischen1995/getting-started]
An image does not exist locally with the tag: louischen1995/getting-started
```
為何失敗？這條指令會去尋找一個名稱為`louischen1995/getting-started`的image，可以使用`docker image ls`，你也不會看到有這個image。
為解決這個問題，我們需要將原有的image加上tag，來賦予其他名稱。

2. 登入Docker Hub並使用以下指令`docker login -u YOUR-USER-NAME`
3. 使用`docker tag`指令給予`getting-started`image一個新名字，例如:`docker tag getting-started louischen1995/getting-started`
4. 再push一次，你可以下`tagname`，如果沒有設定就會是預設的`latest`

## Persist the DB
之前的todo list app再重啟container後每次都會清空資料，為何會這樣？以下說明。

### The container's filesystem
當一個container在運行時，他會使用來自image內不同層來作為其檔案系統，各個container也都有自己的暫存空間用來建立/更新/刪除檔案，任何更動都不會影響到其他的container甚至就算他們是使用同一個image。

#### See this in pracitce
To see this in action, we’re going to start two containers and create a file in each. What you’ll see is that the files created in one container aren’t available in another.

1. Start an ubuntu container that will create a file named /data.txt with a random number between 1 and 10000.
```shell
 docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"
```
In case you’re curious about the command, we’re starting a bash shell and invoking two commands (why we have the &&). The first portion picks a single random number and writes it to /data.txt. The second command is simply watching a file to keep the container running.

2. Validate that we can see the output by execing into the container. To do so, open the Dashboard and click the first action of the container that is running the ubuntu image.

Dashboard open CLI into ubuntu container

You will see a terminal that is running a shell in the ubuntu container. Run the following command to see the content of the /data.txt file. Close this terminal afterwards again.


```shell=
cat /data.txt
```
If you prefer the command line you can use the docker exec command to do the same. You need to get the container’s ID (use docker ps to get it) and get the content with the following command.


```shell
docker exec <container-id> cat /data.txt
```
You should see a random number!

Now, let’s start another ubuntu container (the same image) and we’ll see we don’t have the same file.


 docker run -it ubuntu ls /
And look! There’s no data.txt file there! That’s because it was written to the scratch space for only the first container.

Go ahead and remove the first container using the docker `rm -f <container-id> command`

### Container volumes
根據前面的測試，我們知道各個container間的資料是不會互相影響的且重建重啟後資料也不會被保存，但有了volume，就可以改變這一切。
Volumes提供了container一個特定存儲到主機的檔案系統路徑，如果如果container內的路徑被更動過後，在主機上也會看到更動的情況，如果在container重啟時給於相同的volumes 路徑，也會看到相同的資料。

有兩種主要使用volume的方式，兩種方式都會說，以下先使用named volumes，

### Persist the todo data
預設中todo app會將資料存在SQLite Database在`/etc/todos/todo.db`這是在container的檔案系統中，如果對於SQLite不熟悉也不用擔心，他只是個關聯式資料庫，而container會將所有資料存在單一一個檔案中，而這對大規模的app並不是個好選項，待會會提到如何些換不同的資料庫引擎。

隨著資料庫為單一檔案時，如果我們可以保存這個檔案在主機上並讓下一個container所使用，它也應該可以接續先從執行過的動作，藉由建立一組volume並將其安裝在檔案儲存的路徑，我們就可以永久儲存資料。

如前面所說，我們會使用一組named volume，簡單想像named volume就是一個籃子裝著所有資料，docker會將其存在主機硬碟上而你只需要記住此volume的名稱。

1. 藉由`docker volume create`指令來建立一組volume
```shell=
docker volume create todo-db
```
2. 停止並刪除先前的container `docker rm -f <id>`
3. 啟用todo app的container，但加上`-v`這標籤來指定安裝volume，使用named volume(todo-db)，並將其放到`/etc/todos`，這會抓取路徑上所有建立的檔案。
```shell=
docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
```
4. 啟用後打開todo app並隨意加上幾筆資料。
5. 再一次停止並刪除先前的container `docker rm -f <id>`
6. 在啟用container，和先前指令一樣。
7. 打開todo app後會看到你剛剛新增的資料，確認docker已經有了保存資料的功能。
8. 再一次停止並刪除。

## Use bind mounts 
在前一章節中，我們講到使用name volumes來保存資料，如果只有單純存入資料和不用管存在哪個位置的話Name volumes是個好選擇。

有了bind mounts，我們可以準確控制主機上的存入位置，這可以被用來存資料，但比較常被用來存提供給container的額外資料，當在開發一個程式時，我們可以使用bind mount方式，來將code存入給container讓其觀察有何發生改變。

### Quick volume type comparisons
Bind mounts 和 named volumes是Docker engine中兩種主要的volume，然而也有其餘的volume drive供其他使用模式(SFTP, Ceph, NetApp, S3, and more)
|                                              | Named Volumes             | Bind Mounts                   |
| -------------------------------------------- | ------------------------- | ----------------------------- |
| Host Location                                | Docker chooses            | You control                   |
| Populates new volume with container contents | my-volume:/usr/local/data | /path/to/data:/usr/local/data |
| Mount Example (using -v)                     | Yes                       | No                            |
| Supports Volume Drivers                      | Yes                       | No                            |

### Start a dev-mode container
要使container支援開發者工作模式，需要做以下幾項設定
- 將程式碼掛載到container
- 安裝所有的dependencies包含"dev" dependencies
- 開啟nodeman 並監看所有檔案變動

1. 確保沒有`getting-started`container在運行
2. 執行下面的命令並看是哪種機器
    如果是x86-64 Mac or Linux
    ```shell=
     docker run -dp 3000:3000 \
     -w /app -v "$(pwd):/app" \
     node:12-alpine \
     sh -c "yarn install && yarn run dev"
    ```
    如果是Windows
    ```shell=
     docker run -dp 3000:3000 `
     -w /app -v "$(pwd):/app" `
     node:12-alpine `
     sh -c "yarn install && yarn run dev"
    ```
    如果是Apple晶片或是其他ARM64架構的
    ```shell=
     docker run -dp 3000:3000 \
     -w /app -v "$(pwd):/app" \
     node:12-alpine \
     sh -c "apk add --no-cache python2 g++ make && yarn install && yarn run dev"
    ```
    - `-dp 3000:3000`: 跟先前相同，detached mode(背景執行)，以及建立port mapping
    - `-w /app`: 設定當前路徑的工作資料夾
    - `-v "$(pwd):/app`: 綁定掛載目前container的路徑到/app這資料內
    - `node:12-alpine`: 使用的基礎image
    - `sh -c "yarn install && yarn run dev"`:apline 沒有`bash`並執行`yarn install`安裝所有的dependencies然後執行`yarn run dev`。

3. 你可以使用`docker logs -f <container-id>`來追蹤logs
4. 在檔案內做變動
```html=
 -    {submitting ? 'Adding...' : 'Add Item'}
 +    {submitting ? 'Adding...' : 'Add'}
```
5. 過幾秒後重整頁面就會看到更新後的資料
6. 當結束開發後，暫停此container並建立新的image
```shell=
docker build -t getting-started .
```

## Multi container apps
到目前，我們已經與單一container互動過了，但接下來要加入MySQL到開發層內，問題是，MySQL要在哪裡運作呢？放在同個container或是分開呢？基本上個別的container應該就只做一件事並將其做好而其原因有:
- 有極大的可能你會將API的開發與前端的開發分開來放相對於資料庫來說。
- 獨立container可以讓你更好掌握版本以及版本的更迭。
- 雖然開發時會在本地container放資料庫，但在產品上現階段通常都是把資料庫放在其他服務上(GCP,AWS,AZURE)，你不會想將你的資料庫隨著app到處移動。
- 運行多個程序需要有程序管理，會相對增加container啟動/關閉的複雜性。
還有其他不同理由...所以我們會將app像下圖那樣進行開發。
![](https://i.imgur.com/iSmD9eY.png)

### Container networking
預設上container會獨立運作且不會知道同個機器上的其他程序、container如何運作，那這樣如何允許container與另外一個container連線呢？答案就是**netwokring**，你不用是個專業的IT網路工程師，你只需要記得簡單的一點...

:::info
如果兩個container是在同一個network環境，他們就可以與對方溝通連線，如果不是同環境則就不行。
:::

### Start MySQL
有兩種方式將兩個container放在同一network，1) 在設定一開始就指定 2)與現有的container連線。
現在先建立一個network，且將MySQL連接上去。
1. 建立network
    ```shell=
    docker network create todo-app
    ```
2. 建立一個MySQL container並將其連接上network。 我們也會定義一些環境變數提供給資料庫初始化使用
下面為ARM架構晶片使用
```shell=
docker run -d \
     --network todo-app --network-alias mysql \
     --platform "linux/amd64" \
     -v todo-mysql-data:/var/lib/mysql \
     -e MYSQL_ROOT_PASSWORD=secret \
     -e MYSQL_DATABASE=todos \
     mysql:5.7
```
裡面的`--network-alias`待會會再說明。
:::info
你會注意到這邊使用了volume named`todo-mysql-data`並將其安裝在了`/var/lib/mysql`，這裡就是MySQL存資料的地方，然而我們從未執行`docker volume create`指令，Docker會自動識別並知道我們想要使用named volume並自動建立。
:::
3. 確認已經建立了資料庫且運行中，連線到資料庫確認。
    ```shell=
    docker exec -it <mysql-container-id> mysql -u root -p
    ```
    接下來輸入密碼**secret**，接下來輸入:
    ```shell=
    SHOW DATABASES;
    ```
    會看到:
    ```sql
     +--------------------+
     | Database           |
     +--------------------+
     | information_schema |
     | mysql              |
     | performance_schema |
     | sys                |
     | todos              |
     +--------------------+
     5 rows in set (0.00 sec)
    ```
    然後離開 `exit`
### Connect to MySQL
現在MySQL已經建立並且運行，但問題是如何使用呢？如果我們將另外一個container運行在同一network，它們要找到對方？(記住雙方都有自己的IP位址)
為了解決這問題，我們要使用[nicolaka/netshoot](https://github.com/nicolaka/netshoot) container，這提供了許多工具用來有效解決且debug network的問題。
1. 建立一個新的container基於 nicolaka/netshoot image，且確保連線到相同的network
    ```shell=
    docker run -it --network todo-app nicolaka/netshoot
    ```
2. 在container內，我們要使用`dig`指令，這是個有用的DNS工具，我們要找到mysql的IP。
    ```shell=
    dig mysql
    ```
    你會得到像以下output
    ```=
     ; <<>> DiG 9.14.1 <<>> mysql
     ;; global options: +cmd
     ;; Got answer:
     ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32162
     ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

     ;; QUESTION SECTION:
     ;mysql.				IN	A

     ;; ANSWER SECTION:
     mysql.			600	IN	A	172.23.0.2

     ;; Query time: 0 msec
     ;; SERVER: 127.0.0.11#53(127.0.0.11)
     ;; WHEN: Tue Oct 01 23:47:24 UTC 2019
     ;; MSG SIZE  rcvd: 44
    ```
    在`ANSWER SECTION`中你會看到`A`紀錄顯示MySQL為`172.23.0.2`（請觀察自己的terminal上顯示的IP)，雖然`mysql`不是一個有效的主機名稱，Docker 還是能夠將其解析為具有該網絡別名的容器的 IP 地址。
    意思就是我們的app只要連到主機名為`mysql`就可以跟資料庫互動了。
    
### Run your app with MySQL
todo app支援設置環境變數的功能，來為MySQL提供連線設定，他們分別是：
- `MYSQL_HOST`:hostname for the running MySQL server
- `MYSQL_USER` - the username to use for the connection
- `MYSQL_PASSWORD` - the password to use for the connection
- `MYSQL_DB` - the database to use once connected

1. 如果是MySQL 8.0或以上請確保下面指令有被加入到`mysql`
```sql=
 ALTER USER 'root' IDENTIFIED WITH mysql_native_password BY 'secret';
 flush privileges;
```

2. ARM架構晶片使用以下
```shell=
 docker run -dp 3000:3000 \
   -w /app -v "$(pwd):/app" \
   --network todo-app \
   -e MYSQL_HOST=mysql \
   -e MYSQL_USER=root \
   -e MYSQL_PASSWORD=secret \
   -e MYSQL_DB=todos \
   node:12-alpine \
   sh -c "apk add --no-cache python2 g++ make && yarn install && yarn run dev"
```

3. 看一下log`docker logs <container-id>`應該會出現下列
```shell=
nodemon src/index.js
 [nodemon] 1.19.2
 [nodemon] to restart at any time, enter `rs`
 [nodemon] watching dir(s): *.*
 [nodemon] starting `node src/index.js`
 Connected to mysql db at host mysql
 Listening on port 3000
```
4. 在瀏覽器打開app並添加一些項目
5. 連線到資料庫
```shell=
 docker exec -it <mysql-container-id> mysql -p todos
```
6. 並運行下列
```sql=
mysql> select * from todo_items;
+--------------------------------------+------+-----------+
| id                                   | name | completed |
+--------------------------------------+------+-----------+
| 50907f37-1704-4935-84cd-15f2c0d2be14 | aaa  |         0 |
| 9c5fb951-2a99-42eb-8dba-3d3804dc81bd | aaa  |         0 |
| b72e77b8-18c3-48b9-9c19-b9978837e1e8 | ddd  |         0 |
| d1a81946-d848-462b-89be-6fbbd59c7f23 | ccc  |         0 |
+--------------------------------------+------+-----------+
4 rows in set (0.00 sec)
```
再來看Docker dashboard，你會看到兩個container正在運行，但沒有明確地表明他們是同一組的，後續會講如何讓其更好。


## Use Docker Compose
Docker Compose的開發是用來協助定義及分享有多個container的應用程式。有了Compose，我們可以建立一個YAML檔案且只需要一個簡單指令來定義服務，可以讓其運作或是停止。
使用Compose的其中一個好處就是你可以在一個檔案中來定義你的應用程式棧，存在project的根目錄，其他人只要clone你的repo並啟用compose app就可以對你的檔案做出貢獻，實際上，你可以看到許多在github上的專案就是這樣執行。

### Install Docker Compose
如果你有Docker Desktop的話，就已經有Docker Compose了，但如果你是使用Linus的話你還要安裝[Docker Compose](https://docs.docker.com/compose/install/)
在安裝後你可以看一下是什麼版本
```shell=
docker compose version
```

### Create the Compose file
1. 在app根目錄建立一個file名為`docker-compose.yml`
2. 在compose檔案內我們先定義架構版本，在多數使用情況，最好是使用最新的版本
```yaml=
 version: "3.7"
```
3. 接下來定義要跑在應用程式內的服務(containers)列表
```yaml=
 version: "3.7"
 services:
```

### Define the app service
以下這是我們定義app container時的指令
```shell=
docker run -dp 3000:3000 \
  -w /app -v "$(pwd):/app" \
  --network todo-app \
  -e MYSQL_HOST=mysql \
  -e MYSQL_USER=root \
  -e MYSQL_PASSWORD=secret \
  -e MYSQL_DB=todos \
  node:12-alpine \
  sh -c "yarn install && yarn run dev"
```

1. 首先，定義container的服務進入點(service entry)和image，可以為service取任何名稱，這名稱會自動成為network別名，在定義MySQL服務時會使用到。
```yaml=
 version: "3.7"

 services:
   app:
     image: node:12-alpine
```
2. 雖然在內容順序上沒有特別規定，但通常來說你會看到`command`在靠近`image`的地方，把這個也加入到file內吧
```yaml=
 version: "3.7"

 services:
   app:
     image: node:12-alpine
     command: sh -c "yarn install && yarn run dev"
```
3. 將port定義加入到service中，這裡使用短語法但也可以使用[長語法](https://docs.docker.com/compose/compose-file/compose-file-v3/#long-syntax-1)來定義不同的動作
```yaml=
 version: "3.7"

 services:
   app:
     image: node:12-alpine
     command: sh -c "yarn install && yarn run dev"
     ports:
       - 3000:3000
```

4. 下一步，把working directory(`-w /app`)和volume mapping(`-v "$(pwd):/app"`)藉由`working_dir`和`volumes`加入定義。
一個使用Docker Compose volume的好處就是可以使用相對路徑根據當前的路徑。
```yaml=
 version: "3.7"

 services:
   app:
     image: node:12-alpine
     command: sh -c "yarn install && yarn run dev"
     ports:
       - 3000:3000
     working_dir: /app
     volumes:
       - ./:/app
```
5. 最後，再把環境變數藉由`enviroment` key加入定義。
```yaml=
 version: "3.7"

 services:
   app:
     image: node:12-alpine
     command: sh -c "yarn install && yarn run dev"
     ports:
       - 3000:3000
     working_dir: /app
     volumes:
       - ./:/app
     environment:
       MYSQL_HOST: mysql
       MYSQL_USER: root
       MYSQL_PASSWORD: secret
       MYSQL_DB: todos
```

### Define the MySQL service
接下來換定義MySQL service, 下面是先前使用過指令。
```shell=
docker run -d \
  --network todo-app --network-alias mysql \
  -v todo-mysql-data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=secret \
  -e MYSQL_DATABASE=todos \
  mysql:5.7
```
1. 先定義一個新的serice並將其命名成`mysql`，他也會自動辨別為network別名，再來定義要使用的image。
```yaml=
version: "3.7"

services:
app:
 # The app service definition
mysql:
 image: mysql:5.7
```
2. 接下來我們要定義volume mapping，先前用`docker run`時，name volume會自動建立，但Compose並不會這樣做，我們需要將volume定義在top-level。
```yaml=
 version: "3.7"

 services:
   app:
     # The app service definition
   mysql:
     image: mysql:5.7
     volumes:
       - todo-mysql-data:/var/lib/mysql

 volumes:
   todo-mysql-data:
```

3. 最後，將環境變數加上去
```yaml=
 version: "3.7"

 services:
   app:
     # The app service definition
   mysql:
     image: mysql:5.7
     volumes:
       - todo-mysql-data:/var/lib/mysql
     environment:
       MYSQL_ROOT_PASSWORD: secret
       MYSQL_DATABASE: todos

 volumes:
   todo-mysql-data:
```

到目前，`docker-compose.yml`看起來應該是這樣：
```yaml=
version: "3.7"

services:
  app:
    image: node:12-alpine
    command: sh -c "yarn install && yarn run dev"
    ports:
      - 3000:3000
    working_dir: /app
    volumes:
      - ./:/app
    environment:
      MYSQL_HOST: mysql
      MYSQL_USER: root
      MYSQL_PASSWORD: secret
      MYSQL_DB: todos

  mysql:
    image: mysql:5.7
    volumes:
      - todo-mysql-data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: todos

volumes:
  todo-mysql-data:
```
### Run the application stack

.
.
.
.
.
.
.
.
.
. TBC


