-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathindex.html
305 lines (253 loc) · 28.6 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
<!DOCTYPE html>
<html>
<head><meta name="generator" content="Hexo 3.9.0">
<!-- hexo-inject:begin --><!-- hexo-inject:end --><meta http-equiv="Content-Type" content="text/html" charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Docker Compose 建置 Web service 起步走入門教學 | TechBridge 技術共筆部落格</title>
<meta name="description" content="TechBridge Weekly 技術週刊團隊是一群對用技術改變世界懷抱熱情的團隊。本技術共筆部落格初期專注於Web前後端、行動網路、機器人/物聯網、數據分析與產品設計等技術分享">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- google-site-verification -->
<meta name="google-site-verification" content="WX_9sZlrIYOEpy8RR7zCoa7-pJk611zZt11BSBUcDVY">
<link rel="stylesheet preload" type="text/css" href="/css/screen.css" as="style">
<link rel="stylesheet preload" type="text/css" href="//fonts.googleapis.com/css?family=Noto+Serif:400,700,400italic|Open+Sans:700,400" as="style">
<!-- Favicons -->
<link rel="apple-touch-icon" href="/img/favicon.ico">
<link rel="icon preload" href="/img/favicon.ico" as="image">
<link rel="alternate" type="application/atom+xml" title="Atom 0.3" href="atom.xml"><!-- hexo-inject:begin --><!-- hexo-inject:end -->
</head>
<body class="post-template">
<!-- hexo-inject:begin --><!-- hexo-inject:end --><header class="site-head" >
<div class="vertical">
<div class="site-head-content inner">
<a class="blog-logo" href="/"><img src="/img/logo-tb-500-500.png" alt="Blog Logo"/></a>
<h1 class="blog-title">TechBridge 技術共筆部落格</h1>
<h2 class="blog-description">var topics = ['Web前後端', '行動網路', '機器人/物聯網', '數據分析', '產品設計', 'etc.']</h2>
<div class="navbar-block">
<span><a href="/">首頁</a></span> / <span><a href="/about/">關於我們</a></span> / <span><a href="http://weekly.techbridge.cc/" target="_blank">技術週刊</a></span> / <span><a href="https://www.facebook.com/techbridge.cc/" target="_blank">粉絲專頁</a></span> / <span><a href="/atom.xml" target="_blank">訂閱RSS </a></span>
<br>
</div>
</div>
</div>
</header>
<main class="content" role="main">
<article class="post">
<span class="post-meta">
<time datetime="2018-09-07T12:23:23.000Z" itemprop="datePublished">
2018-09-07
</time>
|
<a href='/tags/docker/'>docker</a>,
<a href='/tags/容器/'>容器</a>,
<a href='/tags/docker-machine/'>docker machine</a>,
<a href='/tags/docker-compose/'>docker compose</a>,
<a href='/tags/docker-swarm/'>docker swarm</a>,
<a href='/tags/vm/'>vm</a>
</span>
<meta name="generator" content="Docker Compose 建置 Web service 起步走入門教學">
<meta name="og:title" content="Docker Compose 建置 Web service 起步走入門教學">
<meta name="og:description" content="TechBridge Weekly 技術週刊團隊是一群對用技術改變世界懷抱熱情的團隊。本技術共筆部落格初期專注於Web前後端、行動網路、機器人/物聯網、數據分析與產品設計等技術分享。">
<meta name="og:type" content="website">
<meta name="og:image" content="/img/og-cover.png">
<h1 class="post-title">Docker Compose 建置 Web service 起步走入門教學</h1>
<section class="post-content">
<div class="fb-like" data-href="https://www.facebook.com/techbridge.cc" data-layout="button_count" data-action="like" data-size="large" data-show-faces="false" data-share="true"></div>
<hr>
<p><img src="/img/kdchang/docker101/docker-compose-logo.png" alt="Docker101"></p>
<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>身為一個開發者最惱人的莫過於環境建置和部屬應用程式。隨著 Docker 容器和虛擬化技術進步以及 DevOps、Infrastructure as Code 文化的推廣,讓我們可以更容易在不同環境開發部屬並調度(Orchestration)我們的專案應用程式。在 Docker 中,除了 Docker 指令和 Docker Engine 背後的技術外,最重要的莫過於 Docker Machine、Docker Compose 和 Docker Swarm 三劍客了。接下來我們將透過 Docker Compose 來啟動並執行 Python Flask + Redis 網頁人數統計的專案,讓讀者能夠理解 Docker Compose 的優勢和使用方式。那就讓我們開始吧!</p>
<p><img src="/img/kdchang/docker101/docker-compose-services.png" alt="Docker101"></p>
<h1 id="Docker-Compose-簡介"><a href="#Docker-Compose-簡介" class="headerlink" title="Docker Compose 簡介"></a>Docker Compose 簡介</h1><p>一開始我們先了解 Docker Compose 是一個工具可以讓你可以透過一個指令就可以控制所有專案(project)中所需要的 services。Docker Compose 是用 <code>YAML</code> 檔案格式來描述和定義 project 中 services 運作關係,白話來說就是用來管理 Container 的文件檔。</p>
<p>什麼意思呢?</p>
<p>試想一下,我們在開發一個典型的 Web project 時通常不是只有一個 service,有可能需要 app server、database、cache,甚至是 reverse proxy 等 service 才能構成一個可以上線運行的專案,這些 service 往往會需要多個 container 來運行。此時若是使用 Docker CLI 需要手動輸入多少行才能正式啟動一個 project?這時候就是 Docker Compose 發揮功能的時候啦!</p>
<p>我們先來看看,一個基本的 docker-compose.yml 檔案長這樣(YAML 檔案格式,使用空格來縮排,附檔名為 .yml):</p>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">'3'</span> <span class="comment"># 目前使用的版本,可以參考官網:</span></span><br><span class="line"><span class="attr">services:</span> <span class="comment"># services 關鍵字後面列出 web, redis 兩項專案中的服務</span></span><br><span class="line"> <span class="attr">web:</span></span><br><span class="line"> <span class="attr">build:</span> <span class="string">.</span> <span class="comment"># Build 在同一資料夾的 Dockerfile(描述 Image 要組成的 yaml 檔案)成 container</span></span><br><span class="line"> <span class="attr">ports:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">"5000:5000"</span> <span class="comment"># 外部露出開放的 port 對應到 docker container 的 port</span></span><br><span class="line"> <span class="attr">volumes:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">.:/code</span> <span class="comment"># 要從本地資料夾 mount 掛載進去的資料</span></span><br><span class="line"> <span class="attr">links:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">redis</span> <span class="comment"># 連結到 redis,讓兩個 container 可以互通網路</span></span><br><span class="line"> <span class="attr">redis:</span></span><br><span class="line"> <span class="attr">image:</span> <span class="string">redis</span> <span class="comment"># 從 redis image build 出 container</span></span><br></pre></td></tr></table></figure>
<h1 id="Dockerfile-和-Docker-Compose-的差異是?"><a href="#Dockerfile-和-Docker-Compose-的差異是?" class="headerlink" title="Dockerfile 和 Docker Compose 的差異是?"></a>Dockerfile 和 Docker Compose 的差異是?</h1><p>在了解到 Docker Compose 主要是用來描述 Service 之間的相依性和調度方式後,我們來看看同樣初學者會比較容易搞混的觀念:Dockerfile。事實上 Dockerfile 是用來描述映像檔(image)的文件。</p>
<p>所謂的 <code>Image</code>,就是生產 <code>Container</code> 的模版,你可以從 Docker Hub 官方下載或是根據官方的 Image 自己加工後打包成 Image 或是完全自己使用 Dockerfile 描述 Image 內容來製作 Image。而 Container 則是透過 Image 產生隔離的執行環境,稱之為 Container,也就是我們一般用來提供 microservice 的最小單位。</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 這是一個創建 ubuntu 並安裝 nginx 的 image</span></span><br><span class="line"><span class="keyword">FROM</span> ubuntu:<span class="number">16.04</span> <span class="comment"># 從 Docker hub 下載基礎的 image,可能是作業系統環境或是程式語言環境,這邊是 ubuntu 16.04</span></span><br><span class="line"><span class="keyword">MAINTAINER</span> [email protected] <span class="comment"># 維護者</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> apt-get update <span class="comment"># 執行 CMD 指令跑的指令,更新 apt 套件包資訊</span></span></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> apt-get install –y nginx <span class="comment"># 執行 CMD 指令跑的指令,安裝 nginx</span></span></span><br><span class="line"><span class="keyword">CMD</span><span class="bash"> [<span class="string">"echo"</span>, <span class="string">"Nginx Image created"</span>]</span></span><br></pre></td></tr></table></figure>
<p>以上為簡單的 Dockerfile。我們可以看到,只需一個文字檔,就清楚描述一個 Docker image。利於使用版本控制,也可以減少 shell script 的工作量。</p>
<h1 id="透過-Docker-建立-Python-Pageview-App"><a href="#透過-Docker-建立-Python-Pageview-App" class="headerlink" title="透過 Docker 建立 Python Pageview App"></a>透過 Docker 建立 Python Pageview App</h1><p>在建立了 Dockerfile 和 Docker Compose 的基礎觀念後,我們來透過一個簡單 Python Flask + Redis 網頁人數統計的專案讓讀者可以更深刻理解 Docker Compose 的威力。</p>
<p>環境準備:<br>Lniux/MacOS 為主<br>可以輸入指令的終端機(若為 windows 可以使用 <a href="http://cmder.net/" target="_blank" rel="noopener">cmder</a>)<br>到官網安裝 <a href="https://docs.docker.com/install/" target="_blank" rel="noopener">Docker</a><br>基本 Docker 和 Web 知識</p>
<ol>
<li><p>創建專案資料夾</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> mkdir docker-compose-python-flask-redis-counter</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> <span class="built_in">cd</span> docker-compose-python-flask-redis-counter</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>在資料夾下建立 app.py 當做 web app 進入點,裡面有 flask 和 redis 操作,當使用者瀏覽首頁時,redis 會記錄次數,若有 exception 則有 retry 機制</p>
<figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">import</span> redis</span><br><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask</span><br><span class="line"></span><br><span class="line">app = Flask(__name__)</span><br><span class="line">cache = redis.Redis(host=<span class="string">'redis'</span>, port=<span class="number">6379</span>)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_hit_count</span><span class="params">()</span>:</span></span><br><span class="line"> retries = <span class="number">5</span></span><br><span class="line"> <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> <span class="keyword">return</span> cache.incr(<span class="string">'hits'</span>)</span><br><span class="line"> <span class="keyword">except</span> redis.exceptions.ConnectionError <span class="keyword">as</span> exc:</span><br><span class="line"> <span class="keyword">if</span> retries == <span class="number">0</span>:</span><br><span class="line"> <span class="keyword">raise</span> exc</span><br><span class="line"> retries -= <span class="number">1</span></span><br><span class="line"> time.sleep(<span class="number">0.5</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route('/')</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_index</span><span class="params">()</span>:</span></span><br><span class="line"> count = get_hit_count()</span><br><span class="line"> <span class="keyword">return</span> <span class="string">'Yo! 你是第 {} 次瀏覽\n'</span>.format(count)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">"__main__"</span>:</span><br><span class="line"> app.run(host=<span class="string">"0.0.0.0"</span>, debug=<span class="literal">True</span>)</span><br></pre></td></tr></table></figure>
</li>
<li><p>建立套件 requirements.txt 安裝資訊讓 Dockerfile 可以下指令安裝套件</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">flask</span><br><span class="line">redis</span><br></pre></td></tr></table></figure>
</li>
<li><p>建立 Web App 的 Dockerfile</p>
<figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> python:<span class="number">3.4</span>-alpine <span class="comment"># 從 python3.4 基礎上加工</span></span><br><span class="line"><span class="keyword">ADD</span><span class="bash"> . /code <span class="comment"># 將本地端程式碼複製到 container 裡面 ./code 資料夾</span></span></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="bash"> /code <span class="comment"># container 裡面的工作目錄</span></span></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> pip install -r requirements.txt</span></span><br><span class="line"><span class="keyword">CMD</span><span class="bash"> [<span class="string">"python"</span>, <span class="string">"app.py"</span>]</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>用 Docker Compose file 描述 services 運作狀況,我們的專案共有 web 和 redis 兩個 service</p>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">'3'</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line"><span class="attr">web:</span></span><br><span class="line"> <span class="attr">build:</span> <span class="string">.</span></span><br><span class="line"> <span class="attr">ports:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">"5000:5000"</span></span><br><span class="line"> <span class="attr">volumes:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">.:/code</span> <span class="comment"># 把當前資料夾 mount 掛載進去 container,這樣你可以直接在本地端專案資料夾改動檔案,container 裡面的檔案也會更動也不用重新 build image!</span></span><br><span class="line"><span class="attr">redis:</span></span><br><span class="line"> <span class="attr">image:</span> <span class="string">"redis:alpine"</span> <span class="comment"># 從 Docker Hub registry 來的 image</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>用 Docker Compose 執行你的 Web app(-d detached 是在背景執行,可以使用 $ docker ps -a 觀看目前所有 docker container 狀況,使用 <code>$ docker-compose ps</code> 觀看 docker-compose process 狀況)</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> docker-compose up -d</span></span><br></pre></td></tr></table></figure>
<p> 若要終止並移除 container 則可以使用 <code>$ docker-compose down</code></p>
</li>
<li><p>到 <a href="http://127.0.0.1:5000/" target="_blank" rel="noopener">http://127.0.0.1:5000/</a> 觀看成果</p>
</li>
</ol>
<p><img src="/img/kdchang/docker101/docker-compose-flask-redis-demo.png" alt="Docker101"></p>
<h1 id="總結"><a href="#總結" class="headerlink" title="總結"></a>總結</h1><p>以上透過 Docker Compose 來啟動並執行 Python Flask + Redis 網頁人數統計的專案,讓讀者能夠理解 Docker Compose 的優勢和使用方式(Docker Compose 是一個工具可以讓你可以透過一個指令就可以控制所有專案(project)中所需要的 services)。同時也複習了 Dockerfile、Docker Image、Container 相關知識。Ya,自從有了 Docker Compose 在本地開發測試專案更加方便,考試都考一百分了!</p>
<h1 id="參考文件"><a href="#參考文件" class="headerlink" title="參考文件"></a>參考文件</h1><ol>
<li><a href="https://docs.docker.com/compose/gettingstarted/#step-8-experiment-with-some-other-commands" target="_blank" rel="noopener">Get started with Docker Compose</a></li>
<li><a href="https://old-oomusou.goodjack.tw/docker/dockerfile-dockercompose/" target="_blank" rel="noopener">深入淺出 Dockerfile 與 Docker Compose</a></li>
<li><a href="http://www.netadmin.com.tw/article_content.aspx?sn=1712060002" target="_blank" rel="noopener">部署Docker Compose 實例示範定義檔撰寫</a></li>
<li><a href="http://blog.maxkit.com.tw/2017/03/docker-compose.html" target="_blank" rel="noopener">Docker Compose 初步閱讀與學習記錄</a></li>
</ol>
<p>(image via <a href="https://medium.com/skillshare-team/from-docker-compose-to-minikube-d94cbe97acda" target="_blank" rel="noopener">cuelogic</a>、<a href="https://share.zabbix.com/virtualization/docker/docker-compose-yml-v2-format-for-zabbix-3-0" target="_blank" rel="noopener">Zabbix</a>)</p>
<p>關於作者:<br><a href="http://blog.kdchang.cc" target="_blank" rel="noopener">@kdchang</a> 文藝型開發者,夢想是做出人們想用的產品和辦一所心目中理想的學校。A Starter & Maker. JavaScript, Python & Arduino/Android lover.:)</p>
<div>喜歡我們的文章嗎?歡迎分享按讚給予我們支持和鼓勵!</div>
<div class="fb-like" data-href="https://blog.techbridge.cc/2018/09/07/docker-compose-tutorial-intro/index.html" data-layout="button_count" data-action="like" data-size="large" data-show-faces="false" data-share="true"></div>
<br>
<br>
<div class="fb-page" data-href="https://www.facebook.com/techbridge.cc" data-small-header="false" data-adapt-container-width="true" data-hide-cover="false" data-show-facepile="true"><blockquote cite="https://www.facebook.com/techbridge.cc" class="fb-xfbml-parse-ignore"><a href="https://www.facebook.com/techbridge.cc">TechBridge 技術日報</a></blockquote></div>
<br>
</section>
<br>
<hr>
<div>
<h4>訂閱 TechBridge Weekly 技術週刊,每週發送最精華的技術開發、產品設計的資訊給您</h4>
<form class="form-control" method="post" action="https://goodbits.io/e/cab8a418-6b70-48d6-97ea-b5f0ef34b22c" target="_blank">
<input class="form-control" type="text" name="first_name" placeholder="First Name"></input>
<input class="form-control" type="text" name="last_name" placeholder="Last Name"></input>
<div>
<input class="form-control" type="text" name="email" placeholder="Email"></input>
</div>
<br>
<div>
<button class="form-control btn subscribe-btn" type="submit">馬上訂閱技術週刊</button>
</div>
<br>
<label for="">PS. 我們討厭垃圾信,所以我們只提供有價值的內容給您 :)</label>
</form>
</div>
<footer class="post-footer">
<section class="author">
<h4>TechBridge Weekly 技術週刊編輯團隊</h4>
<p>TechBridge Weekly 技術週刊團隊是一群對用技術改變世界懷抱熱情的團隊。本技術共筆部落格初期專注於Web前後端、行動網路、機器人/物聯網、資料科學與產品設計等技術分享。This is TechBridge Weekly Team Tech Blog, which focus on web, mobile, robotics, IoT, Data Science technology sharing.</p>
<span><a href="/2016/03/19/about/">關於我們</a></span> / <span><a href="https://www.techbridge.cc/" target="_blank">技術日報</a></span> / <span><a href="http://weekly.techbridge.cc/" target="_blank">技術週刊</a></span> / <span><a href="https://www.facebook.com/techbridge.cc/" target="_blank">粉絲專頁</a></span> / <span><a href="/atom.xml" target="_blank">訂閱RSS </a></span>
<div class="fb-like" data-href="https://www.facebook.com/techbridge.cc" data-layout="button_count" data-size="large" data-action="like" data-show-faces="false" data-share="true"></div>
</section>
<section class="share">
<h4>Share this post</h4>
<a class="icon-twitter" href="http://twitter.com/share?url=https://blog.techbridge.cc/2018/09/07/docker-compose-tutorial-intro/"
onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
<span class="hidden">Twitter</span>
</a>
<a class="icon-facebook" href="https://www.facebook.com/sharer/sharer.php?u=https://blog.techbridge.cc/2018/09/07/docker-compose-tutorial-intro/"
onclick="window.open(this.href, 'facebook-share','width=580,height=296');return false;">
<span class="hidden">Facebook</span>
</a>
<a class="icon-google-plus" href="https://plus.google.com/share?url=https://blog.techbridge.cc/2018/09/07/docker-compose-tutorial-intro/"
onclick="window.open(this.href, 'google-plus-share', 'width=490,height=530');return false;">
<span class="hidden">Google+</span>
</a>
<iframe src="https://ghbtns.com/github-btn.html?user=TechBridgeHQ&repo=blog-starter-kit&type=star&count=true" frameborder="0" scrolling="0" width="170px" height="20px"></iframe>
</section>
</footer>
<br>
</article>
<nav class="pagination" role="pagination">
<h2>更多優質技術文章</h2>
<a class="newer-posts" href="/2018/09/14/lambda-and-github-api/">
← AWS Lambda + GitHub API + Google Sheet = 自動化簽到系統
</a>
<span class="page-number">•</span>
<a class="older-posts" href="/2018/09/02/learn-with-passion/">
如何充滿熱情地學習 - 以資料結構為例 →
</a>
</nav>
<div id="comment" class="comments-area">
<h1 class="title"><a href="#disqus_comments" name="disqus_comments">留言討論</a></h1>
<div id="disqus_thread">
<noscript>Please enable JavaScript to view the <a href="//disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
</div>
</div>
</main>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-75308642-1', 'auto');
ga('send', 'pageview');
</script>
<footer class="site-footer">
<a class="subscribe icon-feed" href="/atom.xml"><span class="tooltip">Subscribe!</span></a>
<div class="inner">
<section class="copyright">All content copyright <a href="/">TechBridge 技術共筆部落格</a> © 2017 • All rights reserved.</section>
</div>
</footer>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script type="text/javascript" src="/js/jquery.fitvids.js"></script>
<script type="text/javascript" src="/js/index.js"></script>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', '[object Object]']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
<script type="text/javascript">
var disqus_shortname = 'techbridgeweekly';
(function(){
var dsq = document.createElement('script');
dsq.type = 'text/javascript';
dsq.async = true;
dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
}());
</script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/medium-zoom.min.js"></script>
<script>
// NodeList
mediumZoom(document.querySelectorAll('img'));
</script>
<div id="fb-root"></div>
<script>(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.5";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
tex2jax: {
inlineMath: [ ["$","$"], ["\\(","\\)"] ],
skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code'],
processEscapes: true
}
});
MathJax.Hub.Queue(function() {
var all = MathJax.Hub.getAllJax();
for (var i = 0; i < all.length; ++i)
all[i].SourceElement().parentNode.className += ' has-jax';
});
</script>
<script src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script><!-- hexo-inject:begin --><!-- Begin: Injected MathJax -->
<script type="text/x-mathjax-config">
MathJax.Hub.Config("");
</script>
<script type="text/x-mathjax-config">
MathJax.Hub.Queue(function() {
var all = MathJax.Hub.getAllJax(), i;
for(i=0; i < all.length; i += 1) {
all[i].SourceElement().parentNode.className += ' has-jax';
}
});
</script>
<script type="text/javascript" src="">
</script>
<!-- End: Injected MathJax -->
<!-- hexo-inject:end -->
</body>
</html>