---
# System prepended metadata

title: "[雲端教學] Vue 3 專案部署 Cloud Run\_全攻略"
tags: [文章]

---

# [雲端教學] Vue 3 專案部署 Cloud Run 全攻略 - 從本機到 GCP：前端專案一鍵上雲的完整流程指南
![Vue 3 專案部署到 Cloud Run](https://hackmd.io/_uploads/HkNzI0Wmel.png)
平常做前端專案講到部署，大家應該都會想到 Vercel、Netlify 或 GitHub Pages 吧？這一次因為工作上的需要，嘗試了不同的做法，把網站部署在 Google 的 Cloud Run 上。

這篇文章就是想分享我自己實作部署的過程，希望可以幫助跟我一樣的前端夥伴，試試看不一樣的部署方式，也多了解一點雲端的世界。

## Cloud Run
Cloud Run 是 Google Cloud 提供的一個 serverless 容器平台，簡單來說，它可以把「裝在容器裡的應用程式」自動部署上網，不需要自己架伺服器，也不需要擔心流量太多會爆掉。

它有幾個很吸引人的優點：
* 自動擴展：流量多就自動開多一點，流量少就省下資源費用。
* 按量計費：真的只有在有請求時才會付錢，用多少算多少。
* 支援容器：只要把程式包成容器，不管是前端還是後端，都可以跑在 Cloud Run 上。
* 公開存取：預設會生成一個可以對外訪問的 HTTPS 網址。

所以即使只是單純想部署一個 Vue 前端專案，只要學會打包成 Docker image，也能用 Cloud Run 來輕鬆上線！

**Cloud Run 為什麼可以選擇 Artifact Registry / Docker Hub、GitHub、Function？**
當要部署一個應用到 Cloud Run，其實就是讓 Cloud Run「幫我們執行一個容器（Container）」。那這個容器從哪裡來呢？就得從我們指定的來源「拉取（pull）」image 來執行。
![來源選擇](https://hackmd.io/_uploads/HJhJaQsZxl.png)

* GitHub
如果專案放在 GitHub，可以搭配 CI/CD 自動部署。 GitHub Actions 可以在 build 完 image 後直接推送到 Cloud Run。
* Function
這個其實不是「拉 image」的來源，而是另一種部署方式。如果選擇的是 Function，其實是把應用部署成 Cloud Function（無容器的 serverless 執行環境），適合純後端 API。

接下來的範例我們會是使用 Google Cloud 自家的倉庫 Artifact Registry 來做說明。

---

上述段落有提到 image 跟 container，如果對這兩個詞感到陌生，可以想像成在做甜點 🍰：

* Image（映像檔）就像是一個蛋糕的食譜，它定義了怎麼做蛋糕，但本身不能吃。
* Container（容器）是根據那個食譜烤出來的實際蛋糕，是可以吃的、活生生的實體。
我們可以從同一份食譜烤出很多個一模一樣的蛋糕，也可以改一點配方再烤出不同版本的蛋糕。

## 前置作業
在把專案部署到 Cloud Run 之前，我們需要先完成以下幾個步驟：

### 建立 GCP 專案、開啟 Cloud Run API
因為 Cloud Run 是 GCP 提供的服務，所以我們要先建立一個 GCP 專案，才能使用其資源。
另外在使用 Cloud Run 之前，也必須確保有啟用它的 API。我們可以到 GCP Console 網頁上手動啟用，或是執行以下指令來啟用 Cloud Run API：
```bash!
gcloud services enable run.googleapis.com
```
### 安裝好 Google Cloud CLI（`gcloud`）
Google Cloud CLI 是我們接下來要用來執行部署指令的工具，像是 `gcloud run deploy`、`gcloud builds submit` 等。

可參考官方的安裝文件：[Install the gcloud CLI](https://cloud.google.com/sdk/docs/install)

安裝完成後，進行初始化設定：
```bash!
gcloud init
```
這邊就會需要會登入 Google 帳號，選擇 GCP 專案，並設定預設地區。

### 前端專案
這次的前端專案是透過 Vue3 搭配 Vite 建置的，使用的套件管理工具為 NPM，中間的開發流程跳過，我們直接快轉到專案部署的部分。

下圖中是我們現在 `package.json` 裡的腳本指令，目前有區分 development 跟 production 環境，也已經準備好相對應的環境檔案 `.env.development` 跟 `.env.production`。

![package.json 裡的腳本指令](https://hackmd.io/_uploads/SJO5kGsWeg.png)

## Dockerfile
接下來我們需要一個 `Dockerfile` 檔案，把專案包成一個 Docker image 。以下是我們的 Dockerfile，裡面的內容可以根據讀者的需求去做修改。
```dockerfile!
# build stage
FROM node:20-alpine AS build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
ARG BUILD_ENV
RUN npm run build:${BUILD_ENV}

# production stage
FROM nginx:stable-alpine AS production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 8080
CMD ["nginx", "-g", "daemon off;"]
```
這邊透過多階段建置（Multi-stage build）
* 使用 `node:20-alpine` 建置 Vue 3 專案。
* 將建置好的 `dist` 目錄內容複製到 Nginx 的預設目錄中，不包含 node_modules 等開發資源。

將建置和部署分開，可以減少最終 image 的大小。另外可以傳入變數 `${BUILD_ENV}` 來 build 相對應的環境。

## nginx.conf
因為我們在 Docker 裡是用 Nginx 來提供打包後的 dist 檔案，所以需要一個 `nginx.conf` 來正確設定伺服器行為。

> 如果是用 Nginx image 來部署 Vue 專案，就會需要一份 `nginx.conf`，用來設定靜態檔案路由、錯誤處理、快取機制等。最常見的用途是解決 Vue Router 的 history 模式會造成 reload 404 的問題。

```nginx!
server {
  # 指定 Nginx 監聽的 port，這邊是 8080
  listen       8080;
  server_name  localhost;

  client_max_body_size        100m;
  client_body_buffer_size     128k;
  proxy_connect_timeout       5;
  proxy_send_timeout          1800;
  proxy_read_timeout          1800;
  proxy_buffer_size           4k;
  proxy_buffers               4 32k;
  proxy_busy_buffers_size     64k;
  proxy_temp_file_write_size  64k;
  auth_basic "status";
  # 開啟 gzip 壓縮，減少傳輸檔案的大小，加快載入速度
  gzip  on;
  # 只有大於 1KB 的檔案才會被壓縮，太小的檔案壓縮效益不高
  gzip_min_length 1k;
  # 壓縮等級：1~9，數字越高壓縮效果越好，但耗 CPU，5 是推薦的平衡值
  gzip_comp_level 5;
  # 只壓縮這些 MIME 類型的回應內容，圖片類型（如 .png, .jpg）不應壓縮，因為它們本來就壓縮過了
  gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css;
  # 停用 IE6 以下版本的 gzip，因為他們可能無法處理壓縮檔案
  gzip_disable "MSIE [1-6]\.";
  # 加上 Vary: Accept-Encoding 標頭，告訴瀏覽器這是根據使用者是否支援 gzip 所決定的內容格式，有助於 CDN 快取管理。
  gzip_vary on;

  # 主要內容路由設定
  location / {
    root   /usr/share/nginx/html;
    index  index.html index.htm;
    try_files $uri $uri/ /index.html;  # 針對 Vue、React 等 SPA 的設定
  }

  # 錯誤頁面處理
  # 當出現伺服器錯誤（500~504）時，自訂錯誤頁面為 /usr/share/nginx/html/50x.html。這樣使用者會看到設計好的錯誤頁面，而不是預設的 Nginx 頁面。
  error_page   500 502 503 504  /50x.html;
  location = /50x.html {
    root   /usr/share/nginx/html;
  }
}
```

## 部署
最後我們回到 `package.json` 在 script 中補上這三條指令：
```javascript!
"scripts": {
    "docker:build": "docker build . -t LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/PACKAGE[:TAG] --platform linux/amd64",
    "docker:run": "docker run LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/PACKAGE[:TAG]",
    "docker:push": "docker push LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/PACKAGE[:TAG]"
}
```

`LOCATION`：填入 Artifact Registry 的位置，像是 `asia-east1`
`PROJECT_ID`：GCP 專案 ID，我們可以到 GCP 上查找
`REPOSITORY`：在 Artifact Registry 上命名的倉庫名稱，例如：`my-frontend-repo`
`PACKAGE`：要自己命名的 image 名稱，像是：`my-vue-app`
`[:TAG]`：選填，可以當版號標記，像是 `:v1.2.3`，如果沒填的話預設是 `:latest`

成功把 image 推上 Artifact Registry 後，我們可以到 GCP 專案的 Cloud Run 頁面直接手動部署，也可以在本地寫指令。
```javascript!
"deploy:cloudrun": "gcloud run deploy CLOUD_RUN_SERVER --image LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/PACKAGE[:TAG] --platform managed --region LOCATION --allow-unauthenticated"
```
`CLOUD_RUN_SERVER`: 要部署的 Cloud Run 名稱

如果想達到懶人一鍵部署，可以把上述的指令全部整理起來，這樣每次要上版的時候，只要跑 `npm run deploy` 就可以上版囉～
```javascript!
"deploy": "npm run docker:build && npm run docker:push && npm run deploy:cloudrun"
```

---

以上都完成後，就可以成功在 Cloud Run 提供的 URL 看到我們的網站囉 🎉

此文同步發佈於 Medium：[[雲端教學] Vue 3 專案部署 Cloud Run 全攻略](https://medium.com/@liz665319/vue3-deploy-to-cloud-run-guide-f2422fc5480f)