最近(其實就是今天阿阿)在寫 webapp 又遭遇到了困難,是這樣地:
我用 yeoman host 了 project, 終於到了要 deploy 的這一刻惹。
這是候我們要先 grunt build 一下(把圖的 sprite 壓縮阿,code 壓縮,css 壓縮什麼的),然後再把這些東西丟上去 server 就搞定。但是
我不想把 build 出來的 code 也放進 version control 裡面,因為我覺得這種 generate 出來的東西沒有什麼邏輯在裡面,再者長久下來,這些東西會讓整個 repository 變得很肥,clone 一下就要好久。

以下是我 deploy 的設定:

  • Capistrano (是的,我還是用 ruby base 的 capistrano 在 deploy...因為我對 grunt 實在是沒有愛)
  • github

大致上的流程就是,

  1. 寫好 code 並 push 到 github 上面
  2. 在 local 下指令 deploy

這時候呢,Capistrano 就會 ssh 到 server, 然後從 server 上把 github 的 code 拉下來,然後再在 server 上做任何你叫他做的事情 (像是重開 worker 阿什麼的) 然後就可以用了。

回到這篇文章的主題,就是 我不想把 build 出來的 code 也放進 version control 裡面, 所以呢,在 server 上把 code 拉下來的時候就會沒有 build 好的版本。這時候要嘛

  1. 在 local build 好然後再叫 Capistrano 傳到 server 上: 這個作法我覺得相當可怕,因為變成真正在 server 上 run 的 code 是沒有任何版本的資訊的,意思是說有可能我們現在 master 上面是 commit-a, 但是真正在 server 上跑的版本可能是 commit-xyz。到時候如果要 rollback 或者有什麼問題要 trace 的時候有可能會欲哭無淚。

  2. 在 server 上把 grunt, bower 等等開發的環境裝起來,然後在 server 上直接 build: 這個方法好一點,但又會遇到另一個問題就是 node 的版本跳得很快,所以如果 server 上有一個以上的 project 的話,有可能會有 node 的版本撞到的情形。(這樣想起來好像可以直接用 nvm 之類的解決,可是用 docker 就是比較潮阿)

好反正我就用了 docker, 我的目標是:

  • build 出來的 code 不要進 version control, 但本身要有版本對應的資訊
  • 在 server 上 npm install, bower install 要快,之前的 deployment 如果有裝過同樣的東西就不要再重裝
  • 在 server 上不要裝 development environment (像是 bower, node, grunt) 等

解法:

所以我搞了一個有node, grunt, bower, compass 的 docker image,然後在每次的 deployment 的時候,在 server 上用這個 image run 一個 container 來 build
。在 build 的時候,把 project 的目錄 link 到 container 裡面這樣。

所以呢,以 npm install 來說:

docker run -t --rm \ 
-v $(pwd):/app \ 
-v $(pwd)/node_modules:/app/node_modules \ 
-w /app \ 
yeoman-builder:latest \ 
npm install
  • bower install:
docker run -t --rm \

-v $(pwd):/app \ 
-v $(pwd)/bower_components:/app/bower_components \

-w /app yeoman-builder:latest \

bower --allow-root --config.interactive=false install
  • grunt build:
docker run -t --rm \

-v $(pwd):/app \ 
-v $(pwd)/node_modules:/app/node_modules \

-v $(pwd)/bower_components:/app/bower_components \

-w /app yeoman-builder:latest \

grunt build

這樣子就大功告成惹!

小陷阱

  • 我在 AWS EC2 的 micro instance 上面嘗試從 docker hub 上 pull image 上來結果竟然失敗… 但直接把 Dockerfile copy 過去,在 server 上 build image 則沒有問題。我猜有可能是 micro instance 太弱小了所以 memory 之類的被吃完也有可能,但這點我沒有深究。

  • 在 run container 的時候,如果有 symbolic link 要特別再 link 一次,不然在 container 裡面會沒辦法 access。以上面的 project 為例,我在 app/ 裡面建立了一個 symbolic link app/node_modules link 到上一層的一個資料夾(目的是為了在 deployments 之間共用 node modules), 在 run container 的時候雖然有 link app/ (-v $(pwd):/app) 但還是要再另外 link node_modules (-v $(pwd)/node_modules:/app/node_modules) 才行。

Capistrano settings:

  desc 'link `node_modules` and `bower_components` to shared_path'
  after :publishing, :link_shared do
    on roles(:app) do
      execute :ln, '-s', shared_path.join('node_modules'), release_path.join('node_modules')
      execute :ln, '-s', shared_path.join('bower_components'), release_path.join('bower_components')
    end
  end

  desc 'build by docker'
  after :link_shared, :docker_build do
    on roles(:app) do
      within release_path do
        execute :docker, 'run', '-t', '--rm', '-v', '$(pwd):/app',
                         '-v', '$(pwd)/node_modules:/app/node_modules', 
                         '-w', '/app', 'mz026/yeoman-builder:latest', 'npm', 'install'
        execute :docker, 'run', '-t', '--rm', '-v', '$(pwd):/app',
                         '-v', '$(pwd)/bower_components:/app/bower_components', 
                         '-w', '/app', 'mz026/yeoman-builder:latest', 
                         'bower', '--allow-root', '--config.interactive=false', 'install'
        execute :docker, 'run', '-t', '--rm', '-v', '$(pwd):/app',
                         '-v', '$(pwd)/node_modules:/app/node_modules', 
                         '-v', '$(pwd)/bower_components:/app/bower_components', 
                         '-w', '/app', 'mz026/yeoman-builder:latest', 'grunt', 'build'

      end
    end
  end

心得:

Docker hub 可以和 github / bitbucket 連結來做 automated build, 意思是說,我們在 github 上面開一個 repository 裡面放著 Dockerfile,然後從 Docker hub 設定,這樣一來,只要 github 的這個 project 有改動,Docker hub 就會自己去拉新的 code 下來 build image,有點貼心喔!