利用 Dockfile、Docker Compose 建立 LAMP 環境 (PHP、Apache、MySQL)
===
範例原始碼放在 GitHub Repo:[titangene/docker-lamp](https://github.com/titangene/docker-lamp)
## 常用 Docker 指令

圖片來源:[Gitbook - 《Docker —— 從入門到實踐》正體中文版 by Philipzheng](https://philipzheng.gitbooks.io/docker_practice/content/appendix_command/)
## 使用基本 Docker 指令建立 PHP + Apache 環境
### 建立並執行 container
接著使用 `docker run` 指令來建立並執行 container,下面是參數說明:
- `--name my-php-apache`:設定 container 名稱為 `my-php-apache`
- `-d`:container 在背景執行
- `-p 8000:80`:指定一個 port,host 對外開 8000 port,container 對內開 80 port
- `php:7.1-apache`:使用 PHP 官方在 Docker Hub 上提供的 [7.1-apache](https://hub.docker.com/_/php/) tag 的 image
```shell
$ docker run --name my-php-apache -d -p 8000:80 php:7.1-apache
08e5975c8a279087db637782181f458cad2a10d5086454b418301de387c1ceb8
```
### 確定 container 是否成功執行
使用 `docker ps` 指令來確定 container 是否成功執行 (查看正在執行的 container 狀態)
```shell
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
08e5975c8a27 php:7.1-apache "docker-php-entryp..." 11 seconds ago Up 10 seconds 0.0.0.0:8000->80/tcp my-php-apache
```
### 查詢主機的 IP 位址
接著是查詢主機的 IP 位址,如果是 Linux 或 masOS 可使用 `ifconfig` 指令,如果是 Windows 則是使用 `ipconfig` 指令
```shell
$ ifconfig
...
ens33 Link encap:Ethernet HWaddr 00:0c:29:89:a0:57
inet addr:192.168.191.130 Bcast:192.168.191.255 Mask:255.255.255.0
inet6 addr: fe38::ec00:21cf:ce7f:1e8d/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:131354 errors:0 dropped:0 overruns:0 frame:0
TX packets:19549 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:190319852 (190.3 MB) TX bytes:1769853 (1.7 MB)
...
```
### 查看服務頁面顯示 403 錯誤
開啟瀏覽器並輸入網址 `[ip-address]:8000` (Port 是剛剛建立 container 時設定的),會發現出現 403 錯誤,原因是因為我們還沒在 Apache web server 的根目錄 `/var/www/html` 下放首頁 `index.php` 或 `index.html`:

### 進入 container 新增 `index.html`
所以接著要使用 `docker exec` 指令進入 container 來新增 `index.php` (在外部向執行中的 container 內部下指令,此時會呼叫 Container 內部的 shell 程式來執行你下的指令)
成功進入 container 後可以看到預設工作目錄就是 `/var/www/html`,這是因為官方在 image 上已經設定好了 (在 `Dockerfile` 中設定的 `WORKDIR /var/www/html`,詳情請參考官方提供的 [`Dockerfile`](https://github.com/docker-library/php/blob/master/7.1/jessie/apache/Dockerfile) )
```shell
$ docker exec -it my-php-apache bash
root@08e5975c8a27:/var/www/html#
```
接著就在 Apache web server 的根目錄 `/var/www/html` 下新增 `index.php`
```shell
$ echo "<?php phpinfo(); ?>" > index.php
$ cat index.php
<?php phpinfo(); ?>
```
接著輸入 `exit` 指令離開 container
```shell
$ exit
```
### 重新查看服務頁面是否正確顯示
然後開啟瀏覽器並輸入網址 `[ip-address]:8000` 就可以看到 `phpinfo()` 的畫面

### 刪除 container
如果要刪除 container 可以使用以下指令:
```shell
$ docker stop my-php-apache
$ docker rm my-php-apache
```
或是直接使用 `docker rm -f` 強制刪除正在執行的 container
```shell
$ docker rm -f my-php-apache
```
## 建立自己的 Docker Images,並 push 至 Docker Hub
### Commit images
| 簡寫,名稱 | 預設 | 說明 |
| ----------------- | ----- | ---------------- |
| `-a`,`--author` | false | 作者 |
| `-m`,`--message` | false | 提交訊息 |
| `-p`,`--pause` | true | 提交期間暫停執行容器 |
```shell
$ docker commit -a "titangene <titangene.tw@gmail.com>" -m "Add index.php" my-php-apache titangene/php-apache:v1.0
```
### 登入 Docker Hub
```shell
$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username (titangene):
Password:
Login Succeeded
```
### push image 至 Docker Hub
```shell
$ docker push [image]
$ docker push titangene/php-apache:v1.0
The push refers to a repository [docker.io/titangene/php-apache]
b0a5a0170d8f: Pushed
d7ca93da4280: Pushed
3be05f838c62: Pushed
...
v1.0: digest: sha256:ea3454... size: 3242
```
### 使用之前儲存的 image
```shell
$ docker run --name my-php-apache -d -p 8000:80 titangene/php-apache:v1.0
```
## 使用 Dockerfile 建立 PHP + Apache 環境
> 詳情請參考我另外整理的 [Dockerfile 學習筆記 by Titangene](/PnuGE_LtQhSArxhx8Ga3FA) 筆記。
### 什麼是 Dockerfile?
`Dockerfile` 是一個文字檔,其中包含了使用者可以在指令列 (command line) 上使用的所有指令,Docker 透過 `docker build` 指令執行 `Dockerfile` 中的所有指令來自動建置新的 image。
### Dockerfile 能做什麼?
- 可將環境進行版本控制
- 自動建置環境,減少重複步驟
- 自訂建立 image 時要做什麼
- 自訂建立 container 時要做什麼
### 建立目錄
```shell
project
├── Dockerfile
└── src
└── index.php
```
```php
// src/index.php
<?php
echo '<h1>Hello World</h1>';
phpinfo();
?>
```
### Dockerfile
- `FROM php:7.1-apache`:第一條命令必須為 `FROM`,說明使用哪個 image 作為基底 (Base Image)
- `COPY src /var/www/html/`:將 Dockerfile 所在目錄的 `src` 資料夾內的資料複製到 container 內的 `/var/www/html/` 下
- `EXPOSE 80`:開放 80 port
```dockerfile
FROM php:7.1-apache
COPY src /var/www/html/
EXPOSE 80
```
### 利用 Dockerfile 建立 Docker Image
- `-t`:添加標記 (tag)
- 指定此 image 的名稱 (name) 和標記 (tag)
- 格式:`docker build -t name:tag .`
- tag 可用來區分同一個 repository 的不同 image (通常作為版本號)
- EX:Ubuntu repo 中有多個 image,通過 Tag 來區分發行版本,例如 12.04、13.04、14.04 ... 等
- `.` (一點):指定 Dockerfile 所在的路徑
- 這邊的 `.` 點指的是當前目錄,也可以換成 Dockerfile 的絕對路徑
```shell
$ cd project
$ ls
Dockerfile src/
$ docker build -t my-php .
Sending build context to Docker daemon 3.584kB
Step 1/3 : FROM php:7.1-apache
---> b7ce92f2bd78
Step 2/3 : COPY src /var/www/html/
---> 3b6ac8f06072
Step 3/3 : EXPOSE 80
---> Running in 0f36c2370aff
Removing intermediate container 0f36c2370aff
---> 365eace4a8d4
Successfully built 365eace4a8d4
Successfully tagged my-php:latest
```
### 查看目前 image
查看 local 有哪些 image,確定是否有名為 `my-php` 的 Docker Image
```shell
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
my-php latest 365eace4a8d4 18 seconds ago 392MB
php 7.1-apache b7ce92f2bd78 2 days ago 392MB
```
### 使用自建的 image 來新建並啟動 container
```shell
$ docker run --name my-php-apache -d -p 8000:80 my-php
```
### 確定 container 是否成功執行
使用 `docker ps` 指令來確定 container 是否成功執行 (查看正在執行的 container 狀態)
```shell
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
dc194e799ca7 my-php "docker-php-entrypoi…" 49 seconds ago Up 46 seconds 0.0.0.0:8000->80/tcp quirky_booth
```
## Docker Volume
在上一個範例,為了要把 `index.php` 首頁檔放入 Apache web server 的根目錄 `/var/www/html` 下,要先利用 `docker exec` 指令進去 container 裡面操作,但是這樣非常麻煩,有沒有方便的方法?
那就要提到 Docker Volume 的概念:掛載主機上的指定目錄到 container 的指定目錄上 (將主機上的目前目錄 bind 到 container 的指定目錄)。
- ``-v `pwd`:/var/www/html``:將 Host 端的目前目錄 ( `pwd` ) 掛載到 container 的 `/var/www/html` 目錄
```shell
$ cd project/src
$ docker run -d -p 8000:80 -v `pwd`:/var/www/html php:7.1-apache
```
### 修改 `index.php`
```shell
$ vi index.html
```
```php
<?php
echo '<h1>Hello World</h1>';
phpinfo();
?>
```
修改後可以看到 `phpinfo` 前面已出現剛剛加入的 `Hello World` 字串:

## 利用 Docker Compose 建立 LAMP

> 詳情請參考我另外整理的 [Docker Compose 學習筆記 by Titangene](https://hackmd.io/c/H1WITKNUz/%2FBkPoXLJ4M) 筆記。
### 什麼是 Docker Compose?
- 快速建立多個 container 的工具
- 利用 `docker-compose.yml` YAML 檔來管理多個 Docker container
- 並使用 `docker-compose` 指令來啟動、停止和重啟應用,以及應用中的服務和所有依賴服務的 container
### 使用的 Image
- [PHP 7.1 on Apache (httpd)](https://hub.docker.com/r/_/php/)
- [MySQL 5.7](https://hub.docker.com/_/mysql/)
- [phpMyAdmin](https://hub.docker.com/r/phpmyadmin/phpmyadmin/)
### 建立目錄
```shell
project
├── docker-compose.yml
├── mysql
│ ├── Dockerfile
│ └── sql
│ └── testdb.sql
├── php
│ └── Dockerfile
└── www
├── db.php
└── index.php
```
### docker-compose.yml
- `build`:指定要使用的 Dockerfile 所在目錄,Docker Compose 會自動幫你執行 `docker build` 指令的動作
- `image`:直接使用某 Docker image 來建立該 container
- `ports: [hostPort]:[ContainerPort]`:設定 port mapping,指定一個 port,host 對外開 `[hostPort]` port,container 對內開 `[ContainerPort]` port
- `depends_on`:依據依賴順序啟動服務
- `volumes: [hostPath]:[containerPath]`:掛載主機上的指定 `[hostPath]` 目錄到 container 的指定目錄 `[containerPath]` 上
- `environment`:設定環境變數
```yaml
version: '3.3'
services:
phpapache:
#image: titangene/php-apache-mysql:v1.0
build: ./php
ports:
- "80:80"
- "443:443"
depends_on:
- mysql
volumes:
- ./www:/var/www/html
mysql:
build: ./mysql
ports:
- "3306:3306"
volumes:
- ./mysql/data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: admin
#MYSQL_DATABASE: testdb
phpmyadmin:
image: phpmyadmin/phpmyadmin
ports:
- "8080:80"
depends_on:
- mysql
environment:
PMA_HOST: mysql
PMA_PORT: 3306
```
### 利用 Docker Compose 啟動服務
- `-d`:背景執行
```shell
$ docker-compose up -d
Creating dockerlamp_mysql_1 ... done
Creating dockerlamp_phpmyadmin_1 ... done
Creating dockerlamp_phpapache_1 ... done
```
用瀏覽器開啟 `localhost` 就會看到 `index.php` 的頁面:

接著開啟 `localhost:8080` 登入 phpMyAdmin:

接著開啟 `localhost/db.php` 可看到 PHP 能正確讀取到 MySQL 的資料:

### 停止執行、刪除服務
```shell
$ docker-compose stop
Stopping dockerlamp_phpmyadmin_1 ... done
Stopping dockerlamp_phpapache_1 ... done
Stopping dockerlamp_mysql_1 ... done
$ docker-compose rm
Going to remove dockerlamp_phpmyadmin_1, dockerlamp_phpapache_1, dockerlamp_mysql_1
Are you sure? [yN] y
Removing dockerlamp_phpmyadmin_1 ... done
Removing dockerlamp_phpapache_1 ... done
Removing dockerlamp_mysql_1 ... done
```
- `-s`:會先讓 container 停止執行,然後再刪除 container
- `-f`:會自動確認刪除 container (原本會問 Y or N)
```shell
$ docker-compose rm -sf
Stopping dockerlamp_phpmyadmin_1 ... done
Stopping dockerlamp_phpapache_1 ... done
Stopping dockerlamp_mysql_1 ... done
Going to remove dockerlamp_phpmyadmin_1, dockerlamp_phpapache_1, dockerlamp_mysql_1
Removing dockerlamp_phpmyadmin_1 ... done
Removing dockerlamp_phpapache_1 ... done
Removing dockerlamp_mysql_1 ... done
```
### 停止容器並移除由 `up` 建立的容器、網絡、volume、image
`docker-compose down` 指令預設只刪除以下內容:
- 在 Compose 檔案中,定義的服務容器
- 在 Compose 檔案中,定義在 `networks` 欄位的網絡
- 預設網絡 (如果有使用的話)
在 Compose 檔案中,定義在 `external` 欄位的網絡和 volume 不會被刪除
- `--rmi type`:刪除 image,type 必須從下面選一個:
- `all`:刪除任何服務使用的所有 image
- `local`:只刪除沒有在 `image` 欄位設定的自定標籤 image
- `-v`,`--volumes`:刪除 Compose 檔案 `volumes` 欄位上有命名的 volume 和 attach 到 container 的匿名 volume
- `--remove-orphans`:刪除未在 Compose 檔案中定義的服務容器
- `-t`,`--timeout TIMEOUT`:指定 shutdown timeout,以秒為單位,預設為 10 秒
```shell
$ docker-compose down --rmi local
Stopping dockerlamp_phpmyadmin_phpapache_1 ... done
Stopping dockerlamp_phpmyadmin_phpmyadmin_1 ... done
Stopping dockerlamp_phpmyadmin_mysql_1 ... done
Removing dockerlamp_phpmyadmin_phpapache_1 ... done
Removing dockerlamp_phpmyadmin_phpmyadmin_1 ... done
Removing dockerlamp_phpmyadmin_mysql_1 ... done
Removing network dockerlamp_phpmyadmin_default
Removing image dockerlamp_phpmyadmin_mysql
```
### 透過 Docker Compose 設定 network
> 詳情請參考我另外整理的 [透過 Docker Compose 設定 network by Titangene](https://titangene.github.io/article/networking-in-docker-compose.html) 筆記。