arrow_back返回文章列表
技術

我使用的 VPS 部署神器 Kamal2 入門指南

現在,我使用的全端框架主要是 Next.js 16 和 Rails 8。透過使用 Rails,我接觸到了官方推薦的部署工具 Kamal。平時用它在 RN 和阿里雲伺服器上部署專案。用起來很順手和簡單,現在我想給大家也介紹一下 Kamal2 的概念和入門使用方法。

這篇文章不會一開始就講太複雜的內容。我更想先把 Kamal2 最核心的東西講清楚:它到底是什麼、能幫我們做什麼、最基本怎麼上手,以及平時最常用的操作有哪些。最後,我會給一個 Next.js 的使用案例,幫助你把前面的概念落到具體專案裡。

Kamal2 是什麼

如果用一句話概括,Kamal2 就是一個透過 SSH + Docker 來完成應用部署的工具。它不提供像 Heroku 那樣的完整平台能力,也不是 Kubernetes 這種重量級編排系統;它更像是把「部署一組 Docker 容器」這件事整理成一套可重複執行的命令和配置。

Kamal 的核心思路其實很直白:

這也是我比較喜歡 Kamal 的地方。它沒有引入太多額外抽象,你大概知道它在做什麼,所以出了問題也比較容易排查。

Kamal2 適合什麼場景

我覺得 Kamal 特別適合下面這種情況:

換句話說,Kamal 不是幫你「取代 Docker」,而是幫你把 Docker 部署變得更順手。

先理解幾個基本概念

正式開始之前,我建議先記住 Kamal 裡最重要的幾個概念。

config/deploy.yml

這是 Kamal 的主配置文件,部署配置就是從這裡讀取的。你可以把它理解為「這次部署的說明書」:服務名、映像名、伺服器列表、映像倉庫、環境變數、builder、proxy 等內容都寫在這裡。

.kamal/secrets

這是 secrets 檔案的預設位置,Kamal 會從這裡讀取敏感變數。比如映像倉庫密碼,或者 Rails 的 RAILS_MASTER_KEY,通常都不會直接硬編碼在 deploy.yml 裡,而是透過 .kamal/secrets 來注入。

kamal setup

這是首次部署最重要的一條命令。文件裡寫得很清楚,它會連線伺服器、安裝 Docker(如果沒有)、登入映像倉庫、建構映像、推送映像、拉取映像、啟動 kamal-proxy、啟動新容器,並在 GET /up 返回 200 OK 後切換流量。

kamal deploy

這是後續發版最常用的命令。首次 setup 跑通之後,後面的部署通常就用 kamal deploy 完成。

怎麼安裝 Kamal2

如果你本地已經有 Ruby 環境,最直接的安裝方式就是:

bash
gem install kamal

這是官方文件給出的標準安裝方式。如果沒有 Ruby,也可以跑容器化的 Kamal,但官方明確提到這種方式會有一些限制,所以入門階段我更推薦直接用 gem 安裝。

安裝完成之後,可以先看下版本:

bash
kamal version

初始化一個專案

進入你的專案目錄後,執行:

bash
kamal init

這條命令會幫你初始化 Kamal 所需的基本檔案,最重要的就是 config/deploy.yml.kamal/secrets

如果你的專案本身已經有 Dockerfile,那其實就已經具備了很關鍵的前置條件。Kamal 的部署流程預設就是圍繞專案根目錄下的標準 Dockerfile 來建構映像。

最小可用配置

我建議入門時先從一個很小的 config/deploy.yml 開始。官方文件給出的最小範例大概是這樣:

yaml
service: myapp
image: your-registry-user/myapp

servers:
  - 203.0.113.10

registry:
  username: your-registry-user
  password:
    - KAMAL_REGISTRY_PASSWORD

builder:
  arch: amd64

env:
  secret:
    - AUTH_SECRET

這個配置裡,最值得先理解的是下面幾項:

這個階段不用追求配置多完整,先保證最基本的鏈路能跑起來更重要。

配置 secrets

接下來要準備 .kamal/secrets。比如:

bash
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
AUTH_SECRET=your-auth-secret

如果是 Rails 專案,文件範例裡會從 config/master.key 讀取 RAILS_MASTER_KEY。這也是 Rails 和 Kamal 結合得很自然的原因之一:Rails 新專案本身就比較容易接上這套部署方式。

第一次部署會發生什麼

當配置和 secrets 都準備好之後,就可以執行:

bash
kamal setup

這條命令會做很多事,但你可以把它理解成「一次完整的首發部署」。根據官方文件,它至少會做這些動作:

這裡有一個非常重要的小點:健康檢查預設是 GET /up。所以你的應用最好有這樣一個輕量級健康檢查介面,不然新容器可能啟動了,但 Kamal 還是不會把流量切過去。

後續部署就簡單很多

首次 setup 成功之後,後面的更新通常只要執行:

bash
kamal deploy

文件裡已經明確把它作為後續部署命令。你可以把它理解成「正常發版入口」:重新建構映像、推送、拉取、起新容器、健康檢查、切流量、停舊容器。

所以從使用體驗上看,Kamal 最舒服的地方就是:第一次部署稍微多做一點準備,後面就會穩定很多。

常用操作

入門階段,我覺得這幾條命令最實用:

初始化

bash
kamal init

生成基礎部署檔案。

首次部署

bash
kamal setup

把伺服器、Docker、映像、代理和應用第一次整體跑起來。

後續發版

bash
kamal deploy

日常最常用的一條命令。

多環境部署

如果你後面開始區分 stagingproduction,Kamal 支援用 -d 指定 destination,比如:

bash
kamal deploy -d staging

官方文件說明,這時 Kamal 會把 config/deploy.staging.yml 和基礎配置一起合併使用。

這個能力很實用,不過如果你只是剛入門,可以先知道它的存在,不急著馬上用上。

為什麼它和 Rails 很搭

Rails 8 新專案通常已經自帶 Dockerfile,而 Kamal 的部署流程正是圍繞 Dockerfile 展開的。再加上 Rails 生態裡本來就比較自然地接受 Ruby gem 工具鏈,所以從 Rails 接觸到 Kamal,幾乎是順理成章的一件事。

對我自己來說,正是因為先在 Rails 專案裡看到這套方式,我才開始認真把它當成一個長期可用的部署方案看待。

一個 Next.js 的入門使用案例

最後給一個更貼近我日常的例子:如果我要用 Kamal 部署一個 Next.js 專案,我通常會把它做成一個標準 Docker 應用,再交給 Kamal 去發布。

第一步:讓 Next.js 輸出為 standalone

Next.js 文件說明,開啟 output: 'standalone' 後,建構產物會生成 .next/standalone,裡面包含部署執行所需的最小檔案和一個 server.js。這非常適合 Docker 部署,因為它不需要把完整開發環境原樣搬進去。 [nextjs](https://nextjs.org/docs/pages/api-reference/config/next-config-js/output)

next.config.js 裡可以這樣寫:

js
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'standalone',
}

module.exports = nextConfig

需要注意的是,Next.js 文件提到,output: 'standalone' 的執行方式更適合直接使用生成的 server.js,而不是繼續用 next start。 [nextjs](https://nextjs.org/docs/14/app/api-reference/next-config-js/output)

第二步:準備 Dockerfile

一個簡單的 Next.js Dockerfile 可以像這樣:

Dockerfile
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/static ./.next/static
EXPOSE 3000
CMD [\"node\", \"server.js\"]

這裡的重點不是 Docker 技巧本身,而是思路:先把 Next.js 做成一個標準、可執行的容器,再讓 Kamal 接管部署。

第三步:寫 Kamal 配置

例如:

yaml
service: my-next-app
image: your-registry-user/my-next-app

servers:
  - 203.0.113.10

registry:
  username: your-registry-user
  password:
    - KAMAL_REGISTRY_PASSWORD

builder:
  arch: amd64

env:
  clear:
    PORT: 3000
    NODE_ENV: production

這份配置的邏輯很簡單:

第四步:補一個健康檢查介面

因為 Kamal 預設會檢查 GET /up,所以在 Next.js 裡我會加一個最簡單的路由,例如 App Router 下:

ts
export async function GET() {
  return Response.json({ ok: true })
}

把它放到比如 app/up/route.ts,這樣 /up 就能返回一個簡單成功響應。這個介面越輕越好,目的只是告訴 Kamal:容器已經可以接流量了。

第五步:部署

最後就回到 Kamal 的標準流程:

bash
kamal setup

後續更新:

bash
kamal deploy

如果這套流程能在 Next.js 專案裡穩定跑起來,那麼你會發現:Kamal 其實並不在乎你是不是 Rails,它真正關心的是你能不能提供一個標準 Docker 映像,以及一個可檢查健康狀態的 Web 服務。

這篇先到這裡

如果你是像我一樣,從 Rails 8 接觸到 Kamal,再逐漸把這套方式遷移到 Next.js 16 專案裡,那麼 Kamal2 的定位其實很好理解:它不是平台,而是一個把自託管 Docker 部署變得更有秩序的工具。

入門階段,先掌握這些就夠了:

等這些東西跑順了,再去了解多環境、accessories、hooks、複雜代理配置,會更自然一些。