更新: 作者現在更習慣用 Caddy 來實現,這篇僅供參考 owo

前言

相信有些人應該會有「部署 Docker Service 後不知道怎麼 expose」的困擾

要 expose 不同 service 到不同的 host port 再用本機的 Nginx 反代也略顯雞肋 (而且還要寫 nginx config 跟放證書好麻煩)

這篇要教大家怎麼不佔用一堆 host port 也可以 expose 各種 HTTP service

先備條件

  • Docker 基礎 (不會 docker 要 expose 甚麼東西 ww)
  • 每個服務一個 domain / subdomain (因為會用到 SNI)
  • 至少一個 Port expose 到公網上 (通常是 80,有 HTTPS 的話外加一個 443)

正文

第一次部署 Nginx Proxy Manager

  1. 首先部署 Nginx Proxy Manager (docker-compose.yml)

    ** 小提醒: 記得先建好 YAML 裡面 services/app/volumes 指定的檔案/資料夾路徑,Leko 是直接用 Docker Volume 取代
    1
    2
    3
    4
    5
    6
    7
    $ mkdir nginx-proxy-manager
    $ cd nginx-proxy-manager
    /nginx-proxy-manager$ wget https://raw.githubusercontent.com/jc21/nginx-proxy-manager/master/doc/example/docker-compose.yml
    /nginx-proxy-manager$ touch config.json
    /nginx-proxy-manager$ mkdir data
    /nginx-proxy-manager$ mkdir letsencrypt
    /nginx-proxy-manager$ docker-compose up -d
  2. 用瀏覽器連接至 http://localhost:81 進行配置

    在非本機 Server 上配置時請記得不要用 localhost XD 第一次登入的帳密是 admin@example.com/changeme
  3. 新增一個 Docker network 用於 Reversy Proxy 並與 Nginx Proxy Manager 連接

    1
    2
    $ docker network create reverse-proxy
    $ docker network connect reverse-proxy nginxproxymanager_app_1
     

設定容器

  1. 將 Service Container 連接至 Reverse Proxy 用的 Docker network

    這裡以我新寫的專案 bb 為例 (Container 名稱為 bb_bb_1)
    1
    $ docker network connect reverse-proxy bb_bb_1
  2. 在 Nginx Proxy Manager 內設定一個新的 Proxy Host

    Domain Names 填入該 Service 預計要被 Expose 至的 Domain, Scheme 選擇該 HTTP Service 的 Scheme, Forward Hostname 填入 Container name (用 Docker network 連接的用意在此), Forward Port 填入該 Service 在容器內部使用的 Port

    ** 小提醒: 記得要先設定好 Domain 的 A/CNAME 等 Record
    以 bb 為例,設定如下


  3. (選擇性, 建議) SSL 證書 (啟用 HTTPS 才有 SNI 可以用喔)

    切換至 SSL 頁籤,選擇 "Request a new SSL Certificate" 在下面填入你的 Email 並同意 Let's Encrypt ToS ** 小提醒: 記得保持 80 Port 為開啟狀態,不要被防火牆擋住
  4. 按下 Save 讓設定生效,如果有選擇申請 SSL 證書的話在這裡也會一併申請。

  5. 搭啦~ 完成ㄌ!

    可以看到 HTTPS 已經成功上線了,也沒有占用除了 Nginx Proxy Manager 使用的 80、443、81 以外的 Port。 SSL 證書還會自動更新!

備註

其實神奇蹦蹦 Nginx Proxy Manager 還可以拿來加入 HTTP Basic Auth (配合 HTTPS 後其實算蠻安全的)、加入 CORS Header、自動 HTTPS 跳轉等功能,有興趣的話可以玩玩看 Advanced 分區。

驗證登入

CORS

CORS 需要一個有點小 Tricky 的技巧,要在 Custom Location 新增一個 / 的 location 再在裡面的 Advanced (齒輪 Icon) 加入 CORS code。 (範例)
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
set $cors "";
set $options "";
if ( $http_origin ~* ^(https?:\/\/.*\.mydomain1\.com$) ){
set $cors "true";
set $options "${options}1";
}
if ( $http_origin ~* ^(https?:\/\/.*\.mydomain2\.com$) ){
set $cors "true";
set $options "${options}1";
}
if ( $http_origin ~* ^(https?:\/\/localhost:8080$) ){
set $cors "true";
set $options "${options}1";
}
if ($request_method = 'OPTIONS'){
set $options "${options}2";
}
if ($cors = 'true') {
add_header 'Access-Control-Allow-Origin' $http_origin always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PATCH, DELETE';
add_header 'Access-Control-Allow-Headers' 'Authorization,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}

if ($options ~* .*12 ) {
#####Repeat######
add_header 'Access-Control-Allow-Origin' $http_origin always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PATCH, DELETE';
add_header 'Access-Control-Allow-Headers' 'Authorization,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
# Tell client that this pre-flight info is valid for 20 days
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}

自動 HTTPS 跳轉

1
2
3
if ($scheme  = "http") {
return 301 https://$server_name$request_uri;
}