├── .gitignore ├── .travis.yml ├── .vuepress └── config.yml ├── Dockerfile ├── README.md ├── SUMMARY.md ├── Vagrantfile ├── docker-compose.yml ├── docs ├── command-tricks.md └── exercises │ ├── exercises-01-hello-docker.md │ ├── exercises-02-understand-docker-run.md │ ├── exercises-03-port-forwarding.md │ ├── exercises-04-run-command.md │ ├── exercises-05-volume-mapping.md │ ├── exercises-06-using-docker-instead-install-tools.md │ ├── exercises-11-environment-variable.md │ ├── exercises-12-link-container.md │ ├── exercises-13-persistent-image.md │ ├── exercises-14-persistent-container-filesystem.md │ ├── exercises-15-volume-advanced.md │ ├── exercises-16-backup-database.md │ ├── exercises-21-docker-build.md │ ├── exercises-22-optimizing-dockerfile.md │ ├── exercises-23-multi-stage-build.md │ └── exercises-31-docker-compose.md ├── package-lock.json ├── package.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Global/Temp.gitignore 2 | *~ 3 | *.swp 4 | 5 | # Global/OSX.gitignore 6 | .DS_Store 7 | 8 | # Thumbnails 9 | ._* 10 | 11 | # Files that might appear on external disk 12 | .Spotlight-V100 13 | .Trashes 14 | 15 | # Global/Windows.gitignore 16 | Thumbs.db 17 | Desktop.ini 18 | 19 | # NetBeans project files 20 | /nbproject/* 21 | 22 | # IntelliJ IDEA project files 23 | /.idea 24 | 25 | # Eclipse project files 26 | /.project 27 | /.buildpath 28 | /.settings 29 | 30 | # Node files 31 | /node_modules 32 | 33 | # vuepress dist files 34 | /dist 35 | 36 | # Vagrant files 37 | /.vagrant 38 | 39 | # Gitbook pdf artifacts 40 | book.pdf 41 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: linux 2 | dist: xenial 3 | language: node_js 4 | node_js: 14 5 | 6 | branches: 7 | only: 8 | - master 9 | 10 | install: 11 | - yarn 12 | 13 | script: 14 | - yarn build 15 | 16 | cache: yarn 17 | 18 | deploy: 19 | - provider: pages 20 | skip_cleanup: true 21 | github_token: ${GITHUB_ACCESS_TOKEN} 22 | local_dir: dist 23 | on: 24 | branch: master 25 | -------------------------------------------------------------------------------- /.vuepress/config.yml: -------------------------------------------------------------------------------- 1 | title: Docker Workshop 2 | description: 手把手帶領大家入門 Docker 世界 3 | base: /docker-workshop/ 4 | dest: dist 5 | themeConfig: 6 | sidebar: 7 | - title: Docker Basic Exercises Part 1 8 | children: 9 | - /docs/exercises/exercises-01-hello-docker 10 | - /docs/exercises/exercises-02-understand-docker-run 11 | - /docs/exercises/exercises-03-port-forwarding 12 | - /docs/exercises/exercises-04-run-command 13 | - /docs/exercises/exercises-05-volume-mapping 14 | - /docs/exercises/exercises-06-using-docker-instead-install-tools 15 | - title: Docker Basic Exercises Part 2 16 | children: 17 | - /docs/exercises/exercises-11-environment-variable 18 | - /docs/exercises/exercises-12-link-container 19 | - /docs/exercises/exercises-13-persistent-image 20 | - /docs/exercises/exercises-14-persistent-container-filesystem 21 | - /docs/exercises/exercises-15-volume-advanced 22 | - /docs/exercises/exercises-16-backup-database 23 | - title: Docker Build Exercises 24 | children: 25 | - /docs/exercises/exercises-21-docker-build 26 | - /docs/exercises/exercises-22-optimizing-dockerfile 27 | - /docs/exercises/exercises-23-multi-stage-build 28 | - title: Docker Compose 29 | children: 30 | - /docs/exercises/exercises-31-docker-compose 31 | - /docs/command-tricks 32 | repo: 104corp/docker-workshop 33 | repoLabel: GitHub 34 | editLinks: true 35 | editLinkText: 編輯這個頁面 36 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10 2 | 3 | WORKDIR /source 4 | 5 | COPY . . 6 | RUN yarn 7 | 8 | EXPOSE 8080 9 | 10 | CMD ["yarn", "dev"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Docker Workshop 2 | 3 | [![Build Status](https://travis-ci.com/104corp/docker-workshop.svg?branch=master)](https://travis-ci.com/104corp/docker-workshop) 4 | 5 | [Docker](https://www.docker.com/) 工作坊。 6 | 7 | 開始前,先對此工作坊做點基本說明: 8 | 9 | * 主要使用 command line 操作,shell 會以 `bash` 為主 10 | * 文件會以 Mac / Linux 環境為主做說明,不保證 Windows 系統完全可用 11 | * 大多 image 裡面的 user 都會是 root,包括執行 docker 也需要 root(Linux)。為方便入門為前提,會以 root 執行來教學 12 | 13 | > GUI 可以參考 [Portainer](https://www.portainer.io/)、[Kitematic](https://kitematic.com/)、[DockStation](https://dockstation.io/),但本 workshop 說明不會討論 GUI。 14 | 15 | ## 直接在原生系統上安裝 Docker 環境 16 | 17 | 不同環境的安裝方法當然是不同的,但最後驗證是否安裝成功,可以打開終端機輸入下面指令: 18 | 19 | ```bash 20 | docker run hello-world 21 | ``` 22 | 23 | 若沒出現錯誤訊息,且有出現 `Hello from Docker!` 的字眼的話,代表服務有正常啟動,可以開始使用 Docker 了。 24 | 25 | ### Mac 26 | 27 | 使用 [Homebrew](https://docs.brew.sh/Installation) 的 [Cask](https://github.com/Homebrew/homebrew-cask) 安裝(需要權限): 28 | 29 | ```bash 30 | brew cask install docker 31 | ``` 32 | 33 | 或是下載[安裝包](https://hub.docker.com/editions/community/docker-ce-desktop-mac)安裝(需要權限)。 34 | 35 | ### Windows 10 36 | 37 | 參考[官方文件](https://docs.docker.com/docker-for-windows/),下載並安裝 Docker Desktop 即可。 38 | 39 | ### Linux 40 | 41 | 參考[官方文件](https://docs.docker.com/install/),注意必須要是 64-bit 版本才能運行 Docker。 42 | 43 | 或使用懶人包安裝: 44 | 45 | ```bash 46 | sudo curl -fsSL https://get.docker.com/ | sh 47 | sudo usermod -aG docker your-user 48 | ``` 49 | 50 | > 已使用 Vagrant 測試過 [`ubuntu/trusty64`](https://app.vagrantup.com/ubuntu/boxes/trusty64)、[`debian/jessie64`](https://app.vagrantup.com/debian/boxes/jessie64) 可行。[`centos/7`](https://app.vagrantup.com/centos/boxes/7) 需另外啟動 docker daemon `sudo systemctl start docker`。 51 | 52 | ## 使用虛擬機安裝 Docker 環境 53 | 54 | 有時候因為特殊理由,可能會不想或無法(如 Windows 7)在本機安裝 Docker,這時可以考慮使用虛擬機安裝。 55 | 56 | ### Docker Machine 57 | 58 | [Docker Machine](https://docs.docker.com/machine/) 是建立 Docker 虛擬機的首選,預設的 provider 包括以下選擇: 59 | 60 | * [VirtualBox](https://docs.docker.com/machine/drivers/virtualbox/) 61 | * [Hyper-V](https://docs.docker.com/machine/drivers/hyper-v/)(Windows only) 62 | * AWS 63 | 64 | > 使用 AWS 請注意防火牆要開通 local 機器的連線,Docker 使用 tcp 2376 port 連線,同時也注意不要讓其他人能連到這個 port。 65 | 66 | 以 VirtualBox 為例,執行以下指令即可建立一個 Docker 虛擬機: 67 | 68 | ```bash 69 | # 建立 Docker 虛擬機 70 | docker-machine create -d virtualbox my-docker 71 | # 查看這台機器相關的環境參數 72 | docker-machine env my-docker 73 | # export 環境參數,執行 docker 指令即可改直接連線到虛擬機上 74 | eval $(docker-machine env my-docker) 75 | ``` 76 | 77 | ### Vagrant 78 | 79 | [Vagrant](https://www.vagrantup.com/) 可以使用指令管理虛擬機(VM),並使用程式碼來表達環境(Infrastructure-as-code,IaC)。 80 | 81 | Clone 此專案,並使用 `vagrant up` 指令即可得到 Ubuntu trusty 64-bit + Docker CE 的乾淨環境: 82 | 83 | ```bash 84 | vagrant up 85 | vagrant ssh 86 | ``` 87 | 88 | ### AWS 89 | 90 | 除了上述方法外,也可以在 AWS 上使用 [RancherOS](https://github.com/rancher/os) 的 AMI。 91 | 92 | #### Cloud9 93 | 94 | 另一個方法是使用 [AWS Cloud9](https://aws.amazon.com/tw/cloud9/) 服務: 95 | 96 | 1. 起 Cloud9 服務,設定直接用預設值即可 97 | 2. 在 Cloud9 服務,下 `curl ifconfig.co` 指令取得公開 IP: 98 | 3. 因需要練習 port forwarding,所以必須到 EC2 服務裡,找到對應的 instance,再設定 security group 99 | 100 | ### Play with Docker 101 | 102 | 上述方法全部都不行的話,最後一個就是使用 [Play with Docker](https://labs.play-with-docker.com/) 服務,只要準備好 DockerHub 的帳號,即可使用。因為它是一個使用 [DinD](https://hub.docker.com/_/docker/) 做成的線上服務,所以會有兩個問題: 103 | 104 | * 它並沒有保證系統一直都可以用,所以什麼時候會壞,這是無法預期的 105 | * 因為使用 DinD,所以跟 port forwarding 相關的練習是無法使用瀏覽器測試的,不過可以使用 curl 指令測試 106 | 107 | ## 預載 Image 108 | 109 | 若知道如何使用 docker 指令下載 image 的話,可以先執行下面這些指令,先下載比較大的 image,避免當天大家都在下載造成網路過慢。 110 | 111 | ```bash 112 | docker pull composer:1.10 113 | docker pull gradle:6.5 114 | docker pull maven:3.6-alpine 115 | docker pull mysql 116 | docker pull nginx 117 | docker pull node:10-alpine 118 | docker pull node:12-alpine 119 | docker pull php:7.2-alpine 120 | docker pull php:7.3-alpine 121 | docker pull php:7.4-alpine 122 | docker pull python:3.8-alpine 123 | docker pull selenium/hub:3.141.59-iron 124 | docker pull selenium/node-chrome-debug:3.141.59-iron 125 | docker pull selenium/node-firefox-debug:3.141.59-iron 126 | ``` 127 | 128 | ## References 129 | 130 | * [Docker Tutorials and Labs](https://github.com/docker/labs) 131 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | * Docker Basic Exercises Part 1 2 | + [Hello Docker](/docs/exercises/exercises-01-hello-docker.md) 3 | + [Understand Docker Run](/docs/exercises/exercises-02-understand-docker-run.md) 4 | + [Port forwarding](/docs/exercises/exercises-03-port-forwarding.md) 5 | + [Run Command](/docs/exercises/exercises-04-run-command.md) 6 | + [Volume Mapping](/docs/exercises/exercises-05-volume-mapping.md) 7 | + [Using Docker Instead Install Tools](/docs/exercises/exercises-06-using-docker-instead-install-tools.md) 8 | * Docker Basic Exercises Part 2 9 | + [Environment Variable](/docs/exercises/exercises-11-environment-variable.md) 10 | + [Link Container](/docs/exercises/exercises-12-link-container.md) 11 | + [Persistent Image](/docs/exercises/exercises-13-persistent-image.md) 12 | + [Persistent Container filesystem](/docs/exercises/exercises-14-persistent-container-filesystem.md) 13 | + [Volume Advanced](/docs/exercises/exercises-15-volume-advanced.md) 14 | + [Backup Database](/docs/exercises/exercises-16-backup-database.md) 15 | * Docker Build Exercises 16 | + [Docker Build](/docs/exercises/exercises-21-docker-build.md) 17 | + [Optimizing Dockerfile](/docs/exercises/exercises-22-optimizing-dockerfile.md) 18 | + [Multi-stage Build](/docs/exercises/exercises-23-multi-stage-build.md) 19 | * Docker Compose 20 | + [Docker Compose](/docs/exercises/exercises-31-docker-compose.md) 21 | * [常見指令速查表](/docs/command-tricks.md) -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure("2") do |config| 5 | config.vm.box = "ubuntu/trusty64" 6 | 7 | # config.vm.network "forwarded_port", guest: 80, host: 8080 8 | # config.vm.network "private_network", ip: "192.168.33.10" 9 | config.vm.provider "virtualbox" do |vb| 10 | vb.memory = "1024" 11 | end 12 | 13 | config.vm.provision "shell", inline: <<-SHELL 14 | curl -fsSL https://get.docker.com/ | sh 15 | usermod -aG docker vagrant 16 | SHELL 17 | end 18 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | web: 4 | build: . 5 | ports: 6 | - 8080:8080 7 | 8 | networks: 9 | default: 10 | ipam: 11 | config: 12 | - subnet: 172.25.1.0/24 -------------------------------------------------------------------------------- /docs/command-tricks.md: -------------------------------------------------------------------------------- 1 | # 常見指令速查表 2 | 3 | ## 別名表 4 | 5 | 以別名的字母順序排序。 6 | 7 | | 別名 | 指令 | 8 | | --- | --- | 9 | | docker attach | docker container attach | 10 | | docker build | docker image build | 11 | | docker commit | docker container commit | 12 | | docker cp | docker container cp | 13 | | docker create | docker container create | 14 | | docker exec | docker container exec | 15 | | docker images | docker image ls | 16 | | docker logs | docker container logs | 17 | | docker ps | docker container ls | 18 | | docker pull | docker image pull | 19 | | docker rm | docker container rm | 20 | | docker rmi | docker image rm | 21 | | docker run | docker container run | 22 | | docker start | docker container start | 23 | | docker stat | docker container stat | 24 | | docker stop | docker container stop | 25 | 26 | ## 特殊技巧 27 | 28 | 在遇到特定問題時,可以使用這些技巧解決。 29 | 30 | ### SHA1 小技巧 31 | 32 | 假設有一個 SHA1 如下: 33 | 34 | ``` 35 | f4e0e10d3b278de232af549e1d6332e64eb3734ffbbcbc63e41dfce96c36d6d4 36 | ``` 37 | 38 | 若有使用過 Git 的話,應該會知道 SHA1 至少要打四個字,才能拿來使用,如: 39 | 40 | ``` 41 | git checkout f4e0 42 | ``` 43 | 44 | 但 Docker 最少只要打一碼就行了: 45 | 46 | ``` 47 | docker inspect f 48 | docker rm f 49 | ``` 50 | 51 | > 若有重覆都會提醒,而不會真的做。 52 | 53 | ### 移除所有的 container 54 | 55 | 雖然可以使用 `docker container prune` 來清理已停止的 container。但如果是想把所有 container 移除的話,可以下這個指令: 56 | 57 | ``` 58 | docker rm -vf $(docker ps -aq) 59 | ``` 60 | 61 | ### 移除所有的 image 62 | 63 | 與移除所有 container 類似。 64 | 65 | 使用 `docker image prune` 可以清理未 tag 的 image。但有時候也會有類似的需求是要清理所有 image 66 | 67 | > `docker image prune` 指令說明是 *Remove unused images* 但實際操作的移除對象是未 tag 的 image 68 | 69 | ``` 70 | docker rmi -f $(docker images -q) 71 | ``` 72 | -------------------------------------------------------------------------------- /docs/exercises/exercises-01-hello-docker.md: -------------------------------------------------------------------------------- 1 | # Hello Docker 2 | 3 | 從這個練習,可以了解: 4 | 5 | * image 與 container 的差異 6 | * image 與 container 之間的依賴關係 7 | 8 | ## 指令練習 9 | 10 | ```bash 11 | # 確認本機的 image / container 狀態為何 12 | docker image ls 13 | docker container ls -a 14 | 15 | # 首次執行官方的 hello world 16 | docker run --name hello hello-world 17 | 18 | # 再次確認 image / container 狀態 19 | docker image ls 20 | docker container ls 21 | 22 | # 移除剛剛的 image / container 23 | docker image rm hello-world 24 | docker container rm hello 25 | 26 | # 再次確認 image / container 狀態 27 | docker image ls 28 | docker container ls 29 | ``` 30 | 31 | ## 指令說明 32 | 33 | ### `docker run` 34 | 35 | 建立新的 container 並執行 command,用法如下: 36 | 37 | ``` 38 | docker run [OPTIONS] IMAGE [COMMAND] [ARG...] 39 | ``` 40 | 41 | IMAGE 會先從 local repository 找,有的話就執行;若沒有的話,就會到 remote repository 下載並執行。 42 | 43 | * `--name` 參數為指定 container 名稱,它必須是唯一,若沒指定則會亂數產生([產生器原始碼](https://github.com/moby/moby/blob/master/pkg/namesgenerator/names-generator.go) 44 | 45 | ### `docker image` 46 | 47 | 這是一系列與 image 相關的指令集,直接執行即可看到可用指令的列表。 48 | 49 | ### `docker image ls` 50 | 51 | 查看目前 local repository 所有 image。 52 | 53 | ### `docker image rm` 54 | 55 | 移除指定的 image,用法如下: 56 | 57 | ``` 58 | docker image rm [OPTIONS] IMAGE [IMAGE...] 59 | ``` 60 | 61 | IMAGE 可以是 tag name,或是 SHA1。其他地方所表示的 IMAGE 亦同。 62 | 63 | > 從用法看得出:傳入一個以上的 IMAGE 是可行的。 64 | 65 | ### `docker container` 66 | 67 | 類似 `docker image`,這是 container 相關的指令集。 68 | 69 | ### `docker container ls` 70 | 71 | 列出正在執行中的 container。 72 | 73 | * `-a|--all` 列出所有的 container 74 | 75 | ### `docker container rm` 76 | 77 | 移除指定的 container,用法如下: 78 | 79 | ``` 80 | docker container rm [OPTIONS] CONTAINER [CONTAINER...] 81 | ``` 82 | 83 | CONTAINER 可以是當初 `--name` 參數給的名稱,或是 SHA1。其他地方所表示的 CONTAINER 亦同。 84 | 85 | > 同 `docker image rm`,傳入一個以上的 CONTAINER 是可行的。 86 | 87 | ## 總結 88 | 89 | 一開始了解 Image 與 Container 之間的差異是很重要的,如此才會理解不同的指令會對應到 Docker 不同的元件。 90 | 91 | 本練習可了解,這兩個元件關係與差異如下: 92 | 93 | * Image 不能修改內容,Container 可以 94 | * Image 不能拿來執行,Container 可以 95 | * Container 必須基於 Image 產生 96 | 97 | ## References 98 | 99 | * [hello-world image](https://hub.docker.com/_/hello-world) 100 | -------------------------------------------------------------------------------- /docs/exercises/exercises-02-understand-docker-run.md: -------------------------------------------------------------------------------- 1 | # Understand Docker Run 2 | 3 | 練習完 [Hello Docker](exercises-01-hello-docker.md) 後,再練習此題目,可以了解: 4 | 5 | * `docker run` 指令背後實際做的事 6 | 7 | ## 指令練習 8 | 9 | ```bash 10 | # 確認 image 11 | docker image ls 12 | 13 | # 下載 busybox image 14 | docker image pull busybox 15 | 16 | # 基於 busybox 建立新的 container 17 | docker container create -it --name mycontainer busybox 18 | 19 | # 啟動 container 20 | docker container start -i mycontainer 21 | 22 | # 觀察 container 23 | docker container ls -a 24 | ``` 25 | 26 | > 在執行 container 的過程中,可以使用 `control + p` 與 `control + q` 的連續組合鍵來達成 detach 效果。 27 | 28 | ## 指令說明 29 | 30 | ### `docker image pull` 31 | 32 | 從遠端 repository 下載 image,用法如下: 33 | 34 | ``` 35 | docker image pull [OPTIONS] NAME[:TAG|@DIGEST] 36 | ``` 37 | 38 | TAG 若沒有給的話,預設會使用 latest,意指下面這兩個指令是等價的: 39 | 40 | ```bash 41 | docker image pull busybox 42 | docker image pull busybox:latest 43 | ``` 44 | 45 | ### `docker container create` 46 | 47 | 建立 container。這個指令類似 `docker container run`,但它只建立 container,不執行。也因為兩個指令單純只差在有沒有執行,所以它們的參數幾乎都能共用。 48 | 49 | 用法如下: 50 | 51 | ``` 52 | docker container create [OPTIONS] IMAGE [COMMAND] [ARG...] 53 | ``` 54 | 55 | * `--name` 選項同 `docker container run`,指定 container 名稱 56 | * `-i|--interactive` 是讓 container 的標準輸入保持打開 57 | * `-t|--tty` 選項是告訴 Docker 要分配一個虛擬終端機(pseudo-tty)並綁定到 container 的標準輸入上 58 | 59 | ### `docker container start` 60 | 61 | 啟動容器,與 `docker container create` 合在一起就是 `docker container run`。用法: 62 | 63 | ``` 64 | docker container start [OPTIONS] CONTAINER [CONTAINER...] 65 | ``` 66 | 67 | * `-i|--interactive` 會把標準輸入綁定到容器上。 68 | 69 | > **注意**:這裡的 `--interactive` 參數與 `docker container create` 的 `--interactive` 參數的意義不同,必須要兩個都有打開才能與 `docker container run` 的 `--interactive` 產生一樣的效果。 70 | 71 | ## 總結 72 | 73 | 在 [Hello Docker](exercises-01-hello-docker.md) 的練習,是使用 `docker container run` 直接執行。本次練習則是了解背後它是如何操作不同的元件,來達成執行程式的目的。 74 | 75 | `docker container run` = `docker image pull` + `docker container create` + `docker container start` 76 | 77 | 除此之外,同時也練習了如何使用 `--interactive` 選項與 `--tty` 選項,來進入 container 的環境。 78 | 79 | ## References 80 | 81 | * [Busybox image](https://hub.docker.com/_/busybox) 82 | -------------------------------------------------------------------------------- /docs/exercises/exercises-03-port-forwarding.md: -------------------------------------------------------------------------------- 1 | # Port forwarding 2 | 3 | 練習此題,可以了解: 4 | 5 | * Docker Container 隔離特性 6 | * 如何透過 port 存取 Container 提供的服務 7 | 8 | ## 指令練習 9 | 10 | ```bash 11 | # 執行多次,port 都不會打架 12 | docker container run -d nginx:alpine 13 | docker container run -d nginx:alpine 14 | docker container run -d nginx:alpine 15 | docker container run -d nginx:alpine 16 | docker container run -d nginx:alpine 17 | 18 | docker container run -d --name my-web -p 8080:80 nginx:alpine 19 | docker container stop my-web 20 | docker container rm -vf my-web 21 | ``` 22 | 23 | ## 指令說明 24 | 25 | ### `docker container run` 26 | 27 | * `-d|--detach` 背景執行 container。 28 | * `-p|--publish` 把 container 的 port 公開到 host 上,格式為 `[IP]:[HOST_PORT]:[CONTAINER_PORT]` 29 | 30 | > 已經跑在前景的 container 可以用 Ctrl + P、Ctrl + Q 組合鍵 detach;接著可以再用 `docker container attach` 讓它回到前景 31 | 32 | ### `docker container stop` 33 | 34 | 停止指定的 container,用法: 35 | 36 | ``` 37 | docker container stop [OPTIONS] CONTAINER [CONTAINER...] 38 | ``` 39 | 40 | 此指令會送出 `SIGTERM` 給 container 的主程序,當 timeout(預設 10,可使用 `-t|--time` 參數調整)後會再送出 `SIGKILL`。 41 | 42 | > 類似地,`docker container pause` 是送 `SIGSTOP`;`docker container kill` 則是預設直接送 `SIGKILL`。 43 | 44 | ### `docker container rm` 45 | 46 | 移除指定的 container,用法: 47 | 48 | ``` 49 | docker container rm [OPTIONS] CONTAINER [CONTAINER...] 50 | ``` 51 | 52 | > 可移除多個 container 53 | 54 | * `-f|--force` 如果是執行中的 container,會強制移除(使用 SIGKILL) 55 | * `-v|--volumes` 移除分配給 container 的 volume 56 | 57 | ## 總結 58 | 59 | 了解 Container 的隔離機制是非常重要的。某種程度上,因為它具備了隔離機制,所以我們才有辦法把它當作「輕量的 VM 」來使用。 60 | 61 | ## References 62 | 63 | * [Nginx image](https://hub.docker.com/_/nginx) 64 | -------------------------------------------------------------------------------- /docs/exercises/exercises-04-run-command.md: -------------------------------------------------------------------------------- 1 | # Run Command 2 | 3 | 練習此題,可以了解: 4 | 5 | * 如何在 `docker run` 指令,直接對 container 下指令 6 | * 如何「進入」container,並處理裡面的設定與檔案等 7 | * Container 的「一次性」特性 8 | 9 | ## 指令練習 10 | 11 | ```bash 12 | # 查看不同版本的 image 的程式版本 13 | docker run --rm -it php:7.3-alpine php -v 14 | docker run --rm -it php:7.4-alpine php -v 15 | docker run --rm -it node:10-alpine node -v 16 | docker run --rm -it node:12-alpine node -v 17 | 18 | 19 | # 啟動 nginx 20 | docker container run -d --name my-web -p 8080:80 nginx:alpine 21 | 22 | # 另外開 terminal 23 | # 「進入」nginx container 24 | docker container exec -it my-web bash 25 | 26 | # 在 nginx container 裡存入一個新的 html 檔 27 | # 存之前與存之後可以查看 http://localhost:8080/my-web.html 網址內容 28 | echo "hello world" > /usr/share/nginx/html/my-web.html 29 | 30 | # 離開 container 並移除 31 | docker container rm -f my-web 32 | 33 | # 重新啟動 nginx 34 | docker container run -d --name my-web -p 8080:80 nginx:alpine 35 | 36 | # 查看 http://localhost:8080/my-web.html 網址內容 37 | ``` 38 | 39 | 在這個練習題目會發現,第一次進入 Nginx container 修改的內容,在重新啟動 Nginx 後會全部消失。這代表 container 是一次性的(disposable)。 40 | 41 | 這個特性對開發或測試而言是好的體驗,類似電腦壞了就重開機解決,環境壞了就砍掉重練。但維運就不一定如此,在具備非一次性的環境管理經驗前提下,這個特性遇到大量佈署情境時,將會面臨一些問題: 42 | 43 | * 客製化設定檔(如 `nginx.conf`)在重啟 container 後,將會消失 44 | * 程式檔每次啟動 container 時,都需要再次佈署程式 45 | * 環境設定如 IP,都是在啟動 container 的時候,才會知道的,該如何配置這些設定? 46 | 47 | 這些問題在後續練習中,將會慢慢知道該如何解決。目前的練習,先了解 container 具備這樣的特性即可。 48 | 49 | ## 指令說明 50 | 51 | ### `docker container run` 52 | 53 | * `--rm` 當 container 主程序一結束時,立刻移除 container 54 | 55 | > **注意**:`--rm` 與 `-d|--detach` 兩個選項是互相衝突的。 56 | 57 | ### `docker container exec` 58 | 59 | 在執行中的 container 上,執行新的指令。 60 | 61 | > 參數 `-i` 與 `-t`,跟 `docker container run` 的意義完全相同。 62 | 63 | ## 總結 64 | 65 | 配合 [Port Forwarding](exercises-03-port-forwarding.md) 所提到的隔離特性,加上今天的練習,我們可以想像 Docker 可以做到以下應用: 66 | 67 | * 隨時切換不同的環境,執行想要測試的指令 68 | * 可同時在不同環境下測試,而它們各自的狀態都會是互相隔離的 69 | 70 | ## References 71 | 72 | * [Nginx image](https://hub.docker.com/_/nginx) 73 | * [PHP image](https://hub.docker.com/_/php) 74 | -------------------------------------------------------------------------------- /docs/exercises/exercises-05-volume-mapping.md: -------------------------------------------------------------------------------- 1 | # Volume Mapping 2 | 3 | 練習此題,可以了解: 4 | 5 | * 如何在 container 讀取本機的目錄或檔案 6 | 7 | ## 指令練習 8 | 9 | ```bash 10 | # 產生一個本機的檔案 11 | echo "hello world" > ./my-web.html 12 | 13 | # 啟動 nginx 14 | docker container run --rm -it -p 8080:80 -v `pwd`/my-web.html:/usr/share/nginx/html/my-web.html nginx:alpine 15 | 16 | # 查看 http://localhost:8080/my-web.html 17 | 18 | # 停止容器再啟動一次 19 | docker container run --rm -it -p 8080:80 -v `pwd`/my-web.html:/usr/share/nginx/html/my-web.html nginx:alpine 20 | 21 | # 再次查看 http://localhost:8080/my-web.html 22 | ``` 23 | 24 | 這個練習題目,是解決 [Run Command](exercises-04-run-command.md) 佈署問題最簡單(同時額外的問題也最多)的方法:直接把 host 上某些檔案掛進 container 即可。同個指令開的 container 除了有一樣環境之外,也能有一樣的檔案。 25 | 26 | 這也是本機開發或測試最常使用的佈署方法。 27 | 28 | ## 指令說明 29 | 30 | ### `docker container run` 31 | 32 | * `-v|--volume` 掛載 volume 到這個 container 上,格式為 `[/host]:[/container]:[參數]` 33 | -------------------------------------------------------------------------------- /docs/exercises/exercises-06-using-docker-instead-install-tools.md: -------------------------------------------------------------------------------- 1 | # Using Docker Instead Install Tools 2 | 3 | 這個練習是一個應用題,同時也是一個小技巧,讓開發者不需安裝工具也能使用該工具。 4 | 5 | ## 指令練習 6 | 7 | ```bash 8 | # 使用 Composer 9 | alias composer="docker container run -it --rm -v \$PWD:/source -w /source composer:1.10" 10 | 11 | # 使用 npm 12 | alias npm="docker container run -it --rm -v \$PWD:/source -w /source node:12-alpine npm" 13 | 14 | # 使用 Gradle 15 | alias gradle="docker container run -it --rm -v \$PWD:/source -w /source gradle:6.5 gradle" 16 | 17 | # 使用 Maven 18 | alias mvn="docker container run -it --rm -v \$PWD:/source -w /source maven:3.6-alpine mvn" 19 | 20 | # 使用 pip 21 | alias pip="docker container run -it --rm -v \$PWD:/source -w /source python:3.8-alpine pip" 22 | 23 | # 使用 Go 24 | alias go="docker container run -it --rm -v \$PWD:/source -w /source golang:1.14-alpine go" 25 | 26 | # 使用 Mix 27 | alias mix="docker container run -it --rm -v \$PWD:/source -w /source elixir:1.10-alpine mix" 28 | ``` 29 | 30 | ## 指令說明 31 | 32 | ### `docker container run` 33 | 34 | * `-w|--workdir` 指定預設執行的路徑 35 | 36 | ## References 37 | 38 | * [Composer image](https://hub.docker.com/_/composer) 39 | * [Elixir image](https://hub.docker.com/_/elixir) 40 | * [Golang image](https://hub.docker.com/_/golang) 41 | * [Gradle image](https://hub.docker.com/_/gradle) 42 | * [Node image](https://hub.docker.com/_/node) 43 | * [Maven image](https://hub.docker.com/_/maven) 44 | * [Python image](https://hub.docker.com/_/python) 45 | -------------------------------------------------------------------------------- /docs/exercises/exercises-11-environment-variable.md: -------------------------------------------------------------------------------- 1 | # Environment Variable 2 | 3 | 練習此題,可以了解: 4 | 5 | * 如何在 container 設定環境變數 6 | 7 | ## 指令練習 8 | 9 | ```bash 10 | # 啟動 mysql 並給予 password 環境變數 11 | docker container run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password mysql 12 | 13 | # 啟動 Node.js Interactive shell 14 | docker container run --rm -it -e NODE_ENV=testing node:alpine 15 | 16 | # 查看環境變數 17 | > process.env.NODE_ENV 18 | ``` 19 | 20 | 這個練習題目,是解決 [Run Command](exercises-04-run-command.md) 環境設定問題。與 [Volume Mapping](exercises-05-volume-mapping.md) 類似,同樣的指令可以讓程式取到相同的環境變數,進而執行該環境所應該表現的行為。 21 | 22 | ## 指令說明 23 | 24 | ### `docker container run` 25 | 26 | * `-e|--env` 設定該 container 的環境變數 27 | 28 | ## References 29 | 30 | * [The Twelve Factors - III. Config](https://12factor.net/config) 31 | -------------------------------------------------------------------------------- /docs/exercises/exercises-12-link-container.md: -------------------------------------------------------------------------------- 1 | # Link Container 2 | 3 | 在練習此題之前,都專注在本機如何跟 container 互動。而練習此題之後,可以了解: 4 | 5 | * 如何讓 container 之間互動 6 | 7 | ## 指令練習 8 | 9 | ```bash 10 | # 建立 network 11 | docker network create my-net 12 | 13 | # 啟動 nginx 14 | docker container run -d --name my-web --network my-net -p 8080:80 nginx:alpine 15 | 16 | # 啟動並進入 busybox 17 | docker container run --rm -it --network my-net busybox 18 | 19 | # 從 busybox 存取 nginx 的服務(使用 my-web 作為 hostname) 20 | wget my-web -O - 21 | ``` 22 | 23 | 這個練習題目,也可以解決 [Run Command](exercises-04-run-command.md) 環境設定問題。這樣就能使用 hostname 來連到指定的 container 了。 24 | 25 | 練習中會發現一個奇妙的事:使用瀏覽器在連接 Nginx 時,必須使用 8080 port,但進容器連結 Nginx,則得使用 80 port。這是虛擬機與 [Port Forwarding](exercises-03-port-forwarding.md) 的特性,必須要清楚了解,使用 Docker 或虛擬機才不會搞混目前要使用什麼連接埠。 26 | 27 | ## 指令說明 28 | 29 | ### `docker network` 30 | 31 | 與網路相關的指令集 32 | 33 | ### `docker network create` 34 | 35 | 建立網路設定,用法如下: 36 | 37 | ``` 38 | docker network create [OPTIONS] NETWORK 39 | ``` 40 | 41 | 本範例並沒有帶任何參數,但需要了解的是下面這個: 42 | 43 | * `-d|--driver` 使用的 driver,預設 `bridge`,其他參數可以參考[官網](https://docs.docker.com/network/#network-drivers) 44 | 45 | ### `docker container run` 46 | 47 | * `--network` 指定網路設定 48 | 49 | ## References 50 | 51 | * [The Twelve Factors - III. Config](https://12factor.net/config) 52 | -------------------------------------------------------------------------------- /docs/exercises/exercises-13-persistent-image.md: -------------------------------------------------------------------------------- 1 | # Persistent Image 2 | 3 | 從這個練習,可以了解: 4 | 5 | * 如何把 Docker 列表裡面的 image 轉換成打包檔 6 | * 如何把打包檔轉回 Docker 列表裡面的 image 7 | 8 | ## 指令練習 9 | 10 | ```bash 11 | # 下載 busybox image 12 | docker image pull busybox 13 | 14 | # 將 image 保存成 tar 15 | docker image save busybox > busybox.tar 16 | 17 | # 移除 image 18 | docker image rm busybox 19 | 20 | # 確認 image 不在 21 | docker image ls 22 | 23 | # 把剛剛保存的 image 再載入 Docker repository 24 | docker image load < busybox.tar 25 | ``` 26 | 27 | ## 指令說明 28 | 29 | ### `docker image save` 30 | 31 | 把 image 使用 tar 打包輸出。預設會使用標準輸出(STDOUT),用法: 32 | 33 | ``` 34 | docker image save [OPTIONS] IMAGE [IMAGE...] 35 | ``` 36 | 37 | 因為是使用標準輸出,所以會使用導出(`>`)的方法輸出檔案,也可以使用下面這個參數來取代導出: 38 | 39 | * `-o|--output` 不使用標準輸出,改使用輸出檔案,後面接檔案名稱即可 40 | 41 | ### `docker image load` 42 | 43 | 把打包的 tar 檔載入到 Docker repository 裡。預設會使用標準輸入(STDIN),用法: 44 | 45 | ``` 46 | docker image load [OPTIONS] 47 | ``` 48 | 49 | 類似 save,只是它是使用導入(`<`)來讀取檔案內容,一樣可以使用參數來取代導入: 50 | 51 | * `-i|--input` 不使用標準輸入,改成直接指定檔案 52 | 53 | ## References 54 | 55 | * [比較 save, export 對於映象檔操作差異](https://blog.hinablue.me/docker-bi-jiao-save-export-dui-yu-ying-xiang-dang-cao-zuo-chai-yi/) 56 | -------------------------------------------------------------------------------- /docs/exercises/exercises-14-persistent-container-filesystem.md: -------------------------------------------------------------------------------- 1 | # Persistent Container filesystem 2 | 3 | 從這個練習,可以了解: 4 | 5 | * 如何把 Docker 列表裡面的 container 的檔案結構轉換成打包檔 6 | * 如何把檔案結構打包檔轉回 Docker 列表裡面的 image 7 | 8 | ## 指令練習 9 | 10 | ```bash 11 | # 執行一個 container 12 | docker container run -it --name mycontainer busybox 13 | 14 | # 做點檔案系統的改變 15 | touch somefile 16 | 17 | # 離開並把檔案系統匯出 tar 18 | docker container export mycontainer > my-export.tar 19 | 20 | # 從 tar 導入檔案系統 21 | docker image import - myimage < my-export.tar 22 | 23 | # 查看 image 24 | docker image ls 25 | ``` 26 | 27 | 這個練習需要注意的是,`docker container export` 與 `docker image import`,與 [Persistent Image](exercises-13-persistent-image.md) 的目的都一樣是把 Docker 的系統保存成檔案,但過程與結果大不相同。 28 | 29 | ## 指令說明 30 | 31 | ### `docker container export` 32 | 33 | 把 container 的檔案系統使用 tar 打包輸出。預設會使用標準輸出(STDOUT),用法: 34 | 35 | ``` 36 | docker container export [OPTIONS] CONTAINER 37 | ``` 38 | 39 | 與 save 類似,也有使用導出(`>`)的方法輸出檔案,也有參數可以取代導出: 40 | 41 | * `-o|--output` 不使用標準輸出,改使用輸出檔案,後面接檔案名稱即可 42 | 43 | ### `docker image import` 44 | 45 | 把打包的 tar 檔的檔案系統導入到 Docker repository 裡。預設會使用標準輸入(STDIN),用法: 46 | 47 | ``` 48 | docker image import [OPTIONS] file|URL|- [REPOSITORY[:TAG]] 49 | ``` 50 | 51 | 類似 load,使用導入(`<`)來讀取檔案內容,但它沒有選項可以取代導入,而是改成使用參數的方法。下面是使用導入與不使用的對照範例: 52 | 53 | ```bash 54 | docker image import myimage < my-export.tar 55 | docker image import my-export.tar myimage 56 | ``` 57 | 58 | ## References 59 | 60 | * [比較 save, export 對於映象檔操作差異](https://blog.hinablue.me/docker-bi-jiao-save-export-dui-yu-ying-xiang-dang-cao-zuo-chai-yi/) 61 | -------------------------------------------------------------------------------- /docs/exercises/exercises-15-volume-advanced.md: -------------------------------------------------------------------------------- 1 | # Volume Advanced 2 | 3 | 從這個練習,可以了解: 4 | 5 | * 如何建立 volume 6 | * 如何將 volume 的目錄綁定到 container 裡 7 | 8 | ## 指令練習 9 | 10 | ```bash 11 | # 建立 volume,並命名為 code 12 | docker volume create --name code 13 | docker volume ls 14 | 15 | # 執行 container 綁定 volume,並查看裡面的內容 16 | docker container run --rm -it -v code:/source busybox ls -l /source 17 | 18 | # 執行 container 做點檔案系統的改變 19 | docker container run --rm -it -v code:/source busybox touch /source/somefile 20 | 21 | # 執行 container 查看 volume 的內容 22 | docker container run --rm -it -v code:/source busybox ls -l /source 23 | 24 | # 執行 nginx container 綁定到 html 目錄裡 25 | docker container run -d -v code:/usr/share/nginx/html --name my-web nginx:alpine 26 | 27 | # 查看 my-web 容器的 html 目錄 28 | docker container exec -it my-web ls -l /usr/share/nginx/html 29 | 30 | # 執行新容器,並把 my-web 容器綁定的 volume 綁到這個容器上 31 | docker container run -d --volumes-from my-web --name my-web2 nginx:alpine 32 | 33 | # 查看新的 my-web2 容器的 html 目錄 34 | docker container exec -it my-web2 ls -l /usr/share/nginx/html 35 | ``` 36 | 37 | ## 指令說明 38 | 39 | ### `docker volume create` 40 | 41 | 建立 volume 42 | 43 | * `--name` 指定 volume 名稱 44 | 45 | ### `docker container run` 46 | 47 | * `-v|--volume` 指定 volume 到 container 裡面的某個目錄 48 | * `--volumes-from` 這個參數要接 container,這可以讓新的 container 去共享舊的 container 的 volume 設定。設定包括 [Volume Mapping](exercises-05-volume-mapping.md),與今天提到的手動建立方法。 49 | 50 | ## References 51 | 52 | * [Docker 實戰系列(三):使用 Volume 保存容器內的數據](https://larrylu.blog/using-volumn-to-persist-data-in-container-a3640cc92ce4) 53 | -------------------------------------------------------------------------------- /docs/exercises/exercises-16-backup-database.md: -------------------------------------------------------------------------------- 1 | # Backup Database 2 | 3 | 以下是一個備份資料庫的範例 4 | 5 | ## 指令練習 6 | 7 | ```bash 8 | # 啟動 database 9 | docker container run -d -e MYSQL_ROOT_PASSWORD=password --name db mysql 10 | 11 | # 停止 database 12 | docker container stop db 13 | 14 | # 執行設定好 volume 的 container 15 | docker container run --rm -it --volumes-from db -v `pwd`:/backup busybox 16 | 17 | # 執行備份指令 18 | tar cvf /backup/backup.tar /var/lib/mysql 19 | ``` 20 | 21 | > 註:若理解 [Run Command](exercises-04-run-command.md) 用法的話,就會知道第三行指令與第四行指令是可以接在一起用的。 22 | 23 | ## References 24 | 25 | * [Backup, restore, or migrate data volumes](https://docs.docker.com/storage/volumes/#backup-restore-or-migrate-data-volumes) 26 | -------------------------------------------------------------------------------- /docs/exercises/exercises-21-docker-build.md: -------------------------------------------------------------------------------- 1 | # Docker Build 2 | 3 | 練習的過程會不斷的建置與移除,可以先準備好 [Makefile](https://gist.github.com/MilesChou/c278f180b2c14af44bc752cdb437ab24),下指令會比較簡單。 4 | 5 | ```makefile 6 | #!/usr/bin/make -f 7 | IMAGE := $(shell basename $(shell pwd)) 8 | VERSION := latest 9 | 10 | .PHONY: all build rebuild shell run 11 | 12 | # ------------------------------------------------------------------------------ 13 | 14 | all: build 15 | 16 | build: 17 | docker build -t=$(IMAGE):$(VERSION) . 18 | 19 | rebuild: 20 | docker build -t=$(IMAGE):$(VERSION) --no-cache . 21 | 22 | shell: 23 | docker run --rm -it $(IMAGE):$(VERSION) sh 24 | 25 | run: 26 | docker run --rm -it $(IMAGE):$(VERSION) 27 | ``` 28 | 29 | 寫一個 Dockerfile 的順序如下: 30 | 31 | 1. 準備一個可以成功 build 的 Dockerfile 32 | 2. 撰寫 Dockerfile 三循環 33 | 1. 新增 Dockerfile 指令 34 | 2. 執行 Build,驗證是否正確 35 | 3. [最佳化 Dockerfile](exercises-22-optimizing-dockerfile.md) 36 | 37 | 以下會用上面的方法,來描述如何使用 PHP built-in server,來寫一個 Laravel Skeleton 的 Dockerfile。 38 | 39 | ## Workshop 40 | 41 | 什麼是一定會成功執行的 Dockerfile?只要 `FROM` 存在的 image 即可。[Laravel 5.8](https://github.com/laravel/laravel) 系統環境要求是 PHP 7.1+,因此來使用目前最新的穩定版 PHP 7.3。 42 | 43 | ```dockerfile 44 | FROM php:7.3-alpine 45 | ``` 46 | 47 | 接著可以使用 `docker build` 與 `docker run` 確定該 image 是可以正常執行,且內容是如我們所想的 48 | 49 | ``` 50 | make build 51 | make shell 52 | ``` 53 | 54 | ## 調整路徑 55 | 56 | 一開始這個路徑可能不是我們所想要,所以調整如下: 57 | 58 | ```dockerfile 59 | WORKDIR /source 60 | ``` 61 | 62 | `WORKDIR` 是設定預設工作目錄,也就是 Docker Build,或是 [Run Command](exercises-04-run-command.md) 的預設工作目錄 63 | 64 | ## 安裝 Composer 65 | 66 | PHP 主要使用 Composer 管理套件依賴。查到安裝 composer 的方法如下: 67 | 68 | ```dockerfile 69 | RUN curl -sS https://getcomposer.org/installer | php 70 | RUN mv composer.phar /usr/local/bin/composer 71 | ``` 72 | 73 | `RUN` 為執行指令,其實就是 `docker run`,與 [Run Command](exercises-04-run-command.md) 的效果是一樣的。不過 build 會把執行完的結果 commit 成另一個 image。 74 | 75 | ## 安裝依賴套件 76 | 77 | 接著把程式碼放進 Image 並執行安裝依賴套件的指令 `composer install` 78 | 79 | ```dockerfile 80 | COPY . . 81 | RUN composer install 82 | ``` 83 | 84 | `COPY` 是把本機的檔案複製到 image 裡,使用方法為:`COPY [hostPath] [containerPath]`。 85 | 86 | 要注意這裡有個雷,檔案複製是沒什麼問題的,但目錄的複製就得小心: 87 | 88 | ```dockerfile 89 | COPY somefile /container/path 90 | ``` 91 | 92 | 這個結果預期會是,container 會多一個檔案是 `/container/path/somefile`,結果也如預期。但目錄就不是這樣: 93 | 94 | ```dockerfile 95 | COPY somedir /container/path 96 | COPY somedir/* /container/path 97 | ``` 98 | 99 | 上面兩個指令是等價的,也就是原本預期會多一個 `/container/path/somedir` 目錄,結果卻是 `somedir` 目錄裡面的所有東西全複製到 `/container/path` 下 100 | 101 | 解決方法是改成下面這個指令: 102 | 103 | ```dockerfile 104 | COPY somedir /container/path/somedir 105 | ``` 106 | 107 | ## 設定預設啟動 server 的指令 108 | 109 | 使用 Artisan 可以啟動 server,指令如下: 110 | 111 | ```dockerfile 112 | CMD ["php", "artisan", "serve", "--host", "0.0.0.0", "--port", "8080"] 113 | ``` 114 | 115 | 但執行的時候可能會發現某些問題,如必要的環境變數 `APP_KEY` 沒有設定,於是設定給它: 116 | 117 | ```dockerfile 118 | ENV APP_KEY "base64:ETwf93f5m2aTbg+YxukR3hEiAHzmvKEi0mt605TkMfU=" 119 | ``` 120 | 121 | 最後重新建置並執行,即可正常運作。 122 | 123 | ## 設定 EXPOSE 資訊 124 | 125 | `EXPOSE` 資訊指的是此服務,開放給外部服務的 port 為何,這是 image 上的 metadata。這能提供開發人員或維運人員有效的資訊,包括某些指令也會使用到此 metadata。 126 | 127 | ```dockerfile 128 | EXPOSE 8080 129 | ``` 130 | 131 | ## Dockerfile 132 | 133 | ```dockerfile 134 | FROM php:7.3-alpine 135 | 136 | WORKDIR /source 137 | 138 | RUN curl -sS https://getcomposer.org/installer | php 139 | RUN mv composer.phar /usr/local/bin/composer 140 | 141 | COPY . . 142 | RUN composer install 143 | 144 | ENV APP_KEY "base64:ETwf93f5m2aTbg+YxukR3hEiAHzmvKEi0mt605TkMfU=" 145 | 146 | EXPOSE 8080 147 | 148 | CMD ["php", "artisan", "serve", "--host", "0.0.0.0", "--port", "8080"] 149 | ``` 150 | 151 | ## References 152 | 153 | * [Docker Build](https://docs.google.com/presentation/d/1OrcP6FKFpLwmzPhmFH8-O9SHJEyu-_K69tPw2gqqsHs) | Miles 154 | * [管理貨櫃的碼頭工人-- Docker ( 2/3 )](https://ithelp.ithome.com.tw/articles/10186279) | CI 從入門到入坑 155 | -------------------------------------------------------------------------------- /docs/exercises/exercises-22-optimizing-dockerfile.md: -------------------------------------------------------------------------------- 1 | # Optimizing Dockerfile 2 | 3 | 上一個練習 [Docker Build](exercises-21-docker-build.md) 最後產生的 Dockerfile 如下: 4 | 5 | ```dockerfile 6 | FROM php:7.3-alpine 7 | 8 | WORKDIR /source 9 | 10 | RUN curl -sS https://getcomposer.org/installer | php 11 | RUN mv composer.phar /usr/local/bin/composer 12 | 13 | COPY . . 14 | RUN composer install 15 | 16 | # Use only development environment 17 | ENV APP_KEY "base64:ETwf93f5m2aTbg+YxukR3hEiAHzmvKEi0mt605TkMfU=" 18 | 19 | EXPOSE 8080 20 | 21 | CMD ["php", "artisan", "serve", "--host", "0.0.0.0", "--port", "8080"] 22 | ``` 23 | 24 | 事實上,這個 Dockerfile 存一些問題: 25 | 26 | * 建置過程傳入了太多非必要的檔案(指 `COPY . .`),這會 27 | + 讓建置時間變久 28 | + 增加不穩定因素 29 | + 增加不必要的重覆建置 30 | * `ENV` 使用了機敏資訊(key),代表這會在原始碼裡出現 31 | * 安裝過程「可能」會有不必要的檔案(指 `composer install` 會安裝測試套件) 32 | 33 | 因此會需要最佳化 Dockerfile,方向有下列幾個: 34 | 35 | * 減少 build image 的時間,以調整流程為主 36 | + 也可參考 [.dockerignore](https://docs.docker.com/engine/reference/builder/#dockerignore-file) 37 | * 減少 image 空間與 commit,以調整流程為主 38 | + 也可參考 [Alpine Linux](https://hub.docker.com/_/alpine/) 或 [Multi-stage Builds](https://docs.docker.com/develop/develop-images/multistage-build/) 39 | * 加強 image 的 SaaS 特性,可參考 [The Twelve Factors](https://12factor.net/) 40 | 41 | ## 減少 build context 的大小 42 | 43 | Build context 是執行 build 一開始,會把檔案傳給 Docker Daemon 準備做 build。 44 | 45 | 但因為有些檔案跟建置無關,如: 46 | 47 | * `.vagrant` 48 | 49 | 與建置無關很好理解。但有些則會是我們希望它跟 image 無關,如: 50 | 51 | * `vendor` 52 | 53 | 比方說,為何我們會希望 host 的 vendor 目錄,會跟 image 無關?舉個例子,若 host 的 PHP 版本是 7.3,image 是 7.1 的時候,就很有可能會出問題。如 host 的 PHP 7.3 可能會安裝 PHPUnit 8,但 image PHP 7.1 是不能使用的。 54 | 55 | 這時,我們可以使用 `.dockerignore` 來排除 build context 時的檔案。 56 | 57 | > 用法類似 `.gitignore`。 58 | 59 | ``` 60 | .vagrant 61 | vendor 62 | bootstrap/cache/packages.php 63 | bootstrap/cache/services.php 64 | ``` 65 | 66 | 如此一來,建置速度會加快,且某些內容也比較不會受到 host 檔案影響。 67 | 68 | ## 利用 cache 來減少不必要的重覆建置 69 | 70 | > 在一個指令完成 commit 後,只要下一次遇到在同個 parent,同個 commit 時,就會繼續使用同一個 commit(類似 Git 的 fast forward)。建置過程如果有使用同一個 commit 則會顯示 *Using cache* 的訊息,因此以下會使用 *cache* 來描述這個行為。 71 | > 72 | > 如果不希望使用 cache 的話,則可以在 `docker build` 指令帶入 `--no-cache` 參數即可。 73 | 74 | 在執行 `COPY . .` 時,要出現 Using cache 的額外條件是:來源內容必須要跟 cache 的一樣(Docker 應該有使用類似 md5 演算法,但不確定用了什麼),才會使用 cache。 75 | 76 | 這代表,僅僅只是修改 `README.md` 就會造成 `COPY . .` 重跑,進而讓 `RUN composer install` 也重跑。 77 | 78 | 這問題的思考方向是:了解 `composer install` 的依賴為哪些檔案,先把這些檔案複製進 image 後,再執行 `COPY . .`。 79 | 80 | 因此解決方法,原本的寫法如下: 81 | 82 | ```dockerfile 83 | COPY . . 84 | RUN composer install 85 | ``` 86 | 87 | 改成: 88 | 89 | ```dockerfile 90 | COPY composer.json . 91 | COPY composer.lock . 92 | RUN composer install 93 | 94 | COPY . . 95 | ``` 96 | 97 | 但因為 Laravel 架構設計,所以 `composer install` 無法獨立執行,必須加上一點東西: 98 | 99 | ```dockerfile 100 | COPY composer.json . 101 | COPY composer.lock . 102 | 103 | RUN mkdir -p database/seeds 104 | RUN mkdir -p database/factories 105 | RUN composer install --no-scripts 106 | 107 | COPY . . 108 | 109 | RUN composer install 110 | ``` 111 | 112 | ## ENV 改執行階段傳入 113 | 114 | [Environment](exercises-11-environment-variable.md) 練習可以傳入指定的環境變數。而 Laravel 使用 `.env` 檔載入環境變數,若要改成由執行階段傳入的話,首先得先把 `.env` 忽略: 115 | 116 | ``` 117 | # .dockerignore 118 | .env 119 | ``` 120 | 121 | 接著使用 `docker run` 的另一個參數 `--env-file` 來載入環境變數: 122 | 123 | ``` 124 | docker run --env-file .env laravel 125 | ``` 126 | 127 | 最後即可把 Dockerfile 的環境變數移除。 128 | 129 | ## 精簡 AUFS 的 commit 數 130 | 131 | Docker 的 commit 數量是有限制的,為 127 個,這是精簡的理由之一。另一個更重大的理由是:因為 AUFS 系統特性,只要 commit 數越多,檔案系統的操作就會越慢,因此更需要想辦法來減少 commit 數。 132 | 133 | 以本例來說,把最上面安裝 composer 的過程合併,不失為一個好方法: 134 | 135 | ```dockerfile 136 | RUN set -xe && \ 137 | curl -sS https://getcomposer.org/installer | php && \ 138 | mv composer.phar /usr/local/bin/composer 139 | ``` 140 | 141 | ## 最佳化後的 Dockerfile 142 | 143 | ```dockerfile 144 | FROM php:7.3-alpine 145 | 146 | WORKDIR /source 147 | 148 | RUN set -xe && \ 149 | curl -sS https://getcomposer.org/installer | php && \ 150 | mv composer.phar /usr/local/bin/composer 151 | 152 | COPY composer.json . 153 | COPY composer.lock . 154 | 155 | RUN set -xe && \ 156 | mkdir -p database/seeds && \ 157 | mkdir -p database/factories && \ 158 | composer install --no-scripts 159 | 160 | COPY . . 161 | 162 | RUN composer install 163 | 164 | EXPOSE 8080 165 | 166 | CMD ["php", "artisan", "serve", "--host", "0.0.0.0", "--port", "8080"] 167 | ``` 168 | 169 | ## References 170 | 171 | * [Docker Performance Improvement: Tips and Tricks](https://stackify.com/docker-performance-improvement-tips-and-tricks/) 172 | * [Use the AUFS storage driver](https://docs.docker.com/storage/storagedriver/aufs-driver/) 173 | * [DOCKER基础技术:AUFS](https://coolshell.cn/articles/17061.html) 174 | -------------------------------------------------------------------------------- /docs/exercises/exercises-23-multi-stage-build.md: -------------------------------------------------------------------------------- 1 | # Multi-stage Build 2 | 3 | 上一個練習 [Optimizing Dockerfile](exercises-22-optimizing-dockerfile.md) 最後產生的 Dockerfile 如下: 4 | 5 | ```dockerfile 6 | FROM php:7.3-alpine 7 | 8 | WORKDIR /source 9 | 10 | RUN set -xe && \ 11 | curl -sS https://getcomposer.org/installer | php && \ 12 | mv composer.phar /usr/local/bin/composer 13 | 14 | COPY composer.json . 15 | COPY composer.lock . 16 | 17 | RUN set -xe && \ 18 | mkdir -p database/seeds && \ 19 | mkdir -p database/factories && \ 20 | composer install --no-scripts 21 | 22 | COPY . . 23 | 24 | RUN composer install 25 | 26 | EXPOSE 8080 27 | 28 | CMD ["php", "artisan", "serve", "--host", "0.0.0.0", "--port", "8080"] 29 | ``` 30 | 31 | 這裡還有兩個很難解決的問題 32 | 33 | * Laravel 有使用 npm 套件建置前端程式,但環境只有 PHP,該怎麼辦? 34 | * 最終建置出來的 Dockerfile,並不希望有任何開發或建置工具(如 Composer),該怎麼辦? 35 | 36 | 這兩個問題,可以使用 Multi-stage Build 解決 37 | 38 | ## 移出 Composer 39 | 40 | 參考官方文件,可以改寫成這樣: 41 | 42 | ```dockerfile 43 | FROM php:7.3-alpine AS php_builder 44 | 45 | WORKDIR /source 46 | 47 | RUN set -xe && \ 48 | curl -sS https://getcomposer.org/installer | php && \ 49 | mv composer.phar /usr/local/bin/composer 50 | 51 | COPY composer.json . 52 | COPY composer.lock . 53 | RUN mkdir -p database/seeds 54 | RUN mkdir -p database/factories 55 | RUN composer install --no-dev --no-scripts 56 | 57 | COPY . . 58 | 59 | RUN composer install 60 | 61 | FROM php:7.3-alpine 62 | 63 | WORKDIR /source 64 | 65 | COPY --from=php_builder /source/vendor ./vendor 66 | COPY . . 67 | 68 | EXPOSE 8080 69 | 70 | CMD ["php", "artisan", "serve", "--host", "0.0.0.0", "--port", "8080"] 71 | ``` 72 | 73 | 也因為分成不同階段建構,甚至第一階段的 Composer 安裝還能改寫成如下: 74 | 75 | ```dockerfile 76 | RUN composer install --no-scripts 77 | 78 | COPY . . 79 | 80 | RUN composer install 81 | RUN php vendor/bin/phpunit 82 | 83 | RUN composer install --no-dev 84 | ``` 85 | 86 | ## 加入 npm 87 | 88 | 對有多階段的做法來說,只要加一個建置階段是 Node 環境即可: 89 | 90 | ```dockerfile 91 | FROM php:7.3-alpine AS php_builder 92 | 93 | WORKDIR /source 94 | 95 | RUN set -xe && \ 96 | curl -sS https://getcomposer.org/installer | php && \ 97 | mv composer.phar /usr/local/bin/composer 98 | 99 | COPY composer.json . 100 | COPY composer.lock . 101 | RUN mkdir -p database/seeds 102 | RUN mkdir -p database/factories 103 | RUN composer install --no-scripts 104 | 105 | COPY . . 106 | 107 | RUN composer install 108 | RUN php vendor/bin/phpunit 109 | 110 | RUN composer install --no-dev 111 | 112 | FROM node:10.15-alpine AS npm_builder 113 | 114 | WORKDIR /source 115 | 116 | COPY package.json . 117 | RUN npm install 118 | 119 | COPY . . 120 | 121 | RUN npm run production 122 | 123 | FROM php:7.3-alpine 124 | 125 | WORKDIR /source 126 | 127 | COPY --from=php_builder /source/vendor ./vendor 128 | COPY --from=npm_builder /source/public/js ./public/js 129 | COPY --from=npm_builder /source/public/css ./public/css 130 | COPY --from=npm_builder /source/public/mix-manifest.json ./public 131 | 132 | COPY . . 133 | 134 | EXPOSE 8080 135 | 136 | CMD ["php", "artisan", "serve", "--host", "0.0.0.0", "--port", "8080"] 137 | ``` 138 | 139 | 同時這也是最終的結果。 140 | 141 | ## References 142 | 143 | * [Use multi-stage builds](https://docs.docker.com/develop/develop-images/multistage-build/) 144 | 145 | 其他最佳化後的 Dockerfile 範例參考: 146 | 147 | * [Docker Phalcon](https://github.com/MilesChou/docker-phalcon) 148 | * [PHP with XDebug](https://github.com/MilesChou/docker-xdebug) 149 | * [Docker Image with PHP extensions](https://github.com/104corp/docker-php-testing) 150 | * [Laravel Eloquent Generator](https://github.com/104corp/laravel-eloquent-generator) 151 | -------------------------------------------------------------------------------- /docs/exercises/exercises-31-docker-compose.md: -------------------------------------------------------------------------------- 1 | # Docker Compose 2 | 3 | 參考 Selenium 在 [DockerHub](https://hub.docker.com/u/selenium) 的 image 說明,與 [Selenium 的範例](https://mileschou.github.io/selenium-docker-example/),實作如下的 Docker Compose file: 4 | 5 | ```yaml 6 | version: "3" 7 | 8 | services: 9 | tester: 10 | build: . 11 | working_dir: /source 12 | volumes: 13 | - .:/source 14 | depends_on: 15 | - chrome 16 | - firefox 17 | 18 | hub: 19 | image: selenium/hub:3.141.59-iron 20 | container_name: hub 21 | ports: 22 | - "4444:4444" 23 | 24 | chrome: 25 | image: selenium/node-chrome-debug:3.141.59-iron 26 | volumes: 27 | - /dev/shm:/dev/shm 28 | depends_on: 29 | - hub 30 | environment: 31 | - HUB_HOST=hub 32 | - HUB_PORT=4444 33 | ports: 34 | - "5900:5900" 35 | 36 | firefox: 37 | image: selenium/node-firefox-debug:3.141.59-iron 38 | volumes: 39 | - /dev/shm:/dev/shm 40 | depends_on: 41 | - hub 42 | environment: 43 | - HUB_HOST=hub 44 | - HUB_PORT=4444 45 | ports: 46 | - "5901:5900" 47 | ``` 48 | 49 | 先啟動依賴的容器: 50 | 51 | ```bash 52 | docker-compose up -d hub chrome firefox 53 | ``` 54 | 55 | 接著就能執行測試: 56 | 57 | ```bash 58 | docker-compose run --rm --no-deps tester 59 | ``` 60 | 61 | 這兩個指令,`-d` 和 `--rm` 與 `docker run` 的參數意義相同,`--no-deps` 則是不額外啟動 `depends_on` 的 container。 62 | 63 | ## References 64 | 65 | * [Selenium Docker Example](https://mileschou.github.io/selenium-docker-example/) 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "vuepress": "^1.8.2" 4 | }, 5 | "scripts": { 6 | "dev": "vuepress dev .", 7 | "build": "vuepress build ." 8 | } 9 | } 10 | --------------------------------------------------------------------------------