---
title: "Rails 部署實踐 - 使用 Watchtower 自動更新"
date: 2022-06-17T00:00:00+08:00
publishDate: 2022-06-17T00:00:00Z
lastmod: 2023-09-03T17:41:34+08:00
tags: ["Rails","教學","部署","實作","Rails 部署實踐","Watchtower","自動更新"]
series: "rails-deployment-in-practice"
toc: true
permalink: "https://blog.aotoki.me/posts/2022/06/17/rails-deployment-in-practice-auto-deploy-with-watchtower/"
language: "zh-tw"
---


為了要能夠實現持續部署，我們除了將專案準備好之外，還需要能夠將 Rails 自動的部署到我們想要部署的伺服器上。

採用容器技術的話就不需要煩惱環境的問題，只需要有能夠運行容器的環境即可，在測試環境或者簡單的小專案就可以使用 [Watchtowner](https://containrrr.dev/watchtower/arguments/) 來幫助進行部署，是一種非常簡單又快速的方式。

<!--more-->

## Docker Compose 部署{#deploy-with-docker-compose}

我們在 [Rails 部署實踐 - 撰寫 Docker Compose](https://blog.aotoki.me/posts/2022/03/18/rails-deployment-in-practice-write-docker-compose/) 有介紹過如何撰寫 Docker Compose 的設定來將我們的專案運行起來。

我們使用 Watchtower 的好處就是我們可以直接沿用原有的 Docker Compose 來進行自動更新，這樣在維護的成本上就會變得非常低。

然而，我們大多數時候只希望 Rails 本身會被更新，而不希望非 Rails 以外的被取代掉，因此我們需要對 Watchtower 做一些額外的設定，並且利用容器的標籤（Label）功能標記哪些容器想要被自動更新。

```yaml
# ...
services:
  # ...
  rails:
    image: "registry.example.com/myapp:${VERSION:-latest}"
    restart: unless-stopped
    environment:
      - DATABASE_URL=postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@postgres/$POSTGRES_DB
      - RAILS_MASTER_KEY
    labels:
    - "traefik.enable=true"
    # Hosts
    - "traefik.http.services.rails.loadbalancer.server.port=3000"
    - "traefik.http.routers.rails-http.rule=Host(`demo.aotoki.dev`)"
    - "traefik.http.routers.rails-http.entrypoints=web"
    - "traefik.http.routers.rails-http.middlewares=https-redirect"
    - "traefik.http.routers.rails-https.rule=Host(`demo.aotoki.dev`)"
    - "traefik.http.routers.rails-https.entrypoints=websecure"
    - "traefik.http.routers.rails-https.tls=true"
    - "traefik.http.routers.rails-https.tls.certresolver=letsencrypt"
    # Watchtower
    - "com.centurylinklabs.watchtower.enable=true"
    networks:
      - frontend
      - backend
    depends_on:
      - postgres
# ...
```

基本上跟 [Rails 部署實踐 - 使用 HTTPS 協定加密連線](https://blog.aotoki.me/posts/2022/03/25/rails-deployment-in-practice-enable-https/)我們的設定類似，但是增加了 `com.centurylinklabs.watchtower.enable=true` 這一個標籤，如此一來 Watchtower 就能夠根據這個標籤來判斷是否要自動的去下載最新版本的容器鏡像，並且將其更新。

## 部署 Watchtower

Watchtower 預設是針對整個 Docker 環境偵測的，因此我們可以建立一個專屬於 Watchtower 的 Docker Compose 設定檔來進行管理。

```yaml
version: '3.8'

services:
  watchtower:
    image: containrrr/watchtower
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /root/.docker/config.json:/config.json
      - /etc/localtime:/etc/localtime:ro
    command: --rolling-restart --label-enable --cleanup --interval 60
```

跟官方網站的範例不同的地方在於我們增加了幾個額外的設定。

* `--rolling-restart` 不要停止原本的容器，先開啟新的容器後才清除舊的容器
* `--label-enable` 不要自動更新所有容器，只更新有標記的容器
* `--cleanup` 清除舊的容器，這樣硬碟比較不容易因為預期外的狀況爆滿
* `--interval 60` 每分鐘檢查一次，可以根據 Registry 的 Rate Limit 限制來調整

另一方面，我額外的將 `- /root/.docker/config.json:/config.json` 作為卷宗（Volume）加入到裏面，這主要是用於私有的 Registry 來進行設定的，如果是 GitHub 上面 Open Source 的專案是不需要特別有這條設定。

如果是使用 GitLab Registry 這類私人的伺服器，會需要先進行 `docker login` 的處理讓伺服器可以存取，這樣 Watchtower 也才能正常下載跟檢查容器鏡像的版本。

## 資料庫更新{#database-migration}

當我們變成自動容器鏡像後，就會需要一個可以執行 `db:migrate` 的動作，在一些比較大型的專案中可能會變成由 DBA（Database Administrator）單獨執行 SQL 來管理，也可能會利用像是 Kuberentes 的 Init Container 來運行，然而在 Watchtower 中都無法這樣做，應該如何對應呢？

實際上在許多專案都會採取透過進入點（Entrypoint）腳本的方式來進行初始化，因此可以參考 [Rails 部署實踐 - 容器進入點](https://blog.aotoki.me/posts/2022/05/13/rails-deployment-in-practice-the-entrypoint/)文章中的設計，在啟動伺服器之前執行一次 `db:migrate` 的處理，或者直接使用我設計的 [openbox](https://github.com/elct9620/openbox) Gem，已經預先設計好針對 Rails 的 `db:migrate` 行為，只需要套用後就可以利用 `AUTO_MIGRATION=yes` 的環境變數設定來達成這個功能。

當然，像這樣在進入點啟動時才自動做資料庫更新的機制在比較大型的專案不太適合，也因此比較適合一些專案的前期使用或者比較小的專案，在專案開發初期使用這樣的架構可以非常快的完成部署跟自動化的環境搭建，就能夠非常流暢的專注在功能的開發上。

---

如果想在第一時間收到更新，歡迎[訂閱弦而時習之](https://mailchi.mp/aotoki/rails-deployment-in-practice)在這系列文章更新時收到通知，如果有希望了解的知識，可以利用[Rails 部署實踐回饋表單](https://us4.list-manage.com/survey?u=dd3d68032c0510041f1302539&id=f25e0dc43e&attribution=false)告訴我。

