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,也可以跑 Docker 化的 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

这条命令会做很多事,但你可以把它理解成“一次完整的首发部署”。根据官方文档,它至少会做这些动作:

  1. 通过 SSH 连到服务器。

  2. 如果服务器缺 Docker,就自动安装。

  3. 在本地和远端登录镜像仓库。

  4. 用项目根目录里的 Dockerfile 构建镜像。

  5. 把镜像推送到 registry。

  6. 让服务器拉取镜像。

  7. 确保 kamal-proxy 在 80/443 端口上工作。

  8. 启动新容器。

  9. 等待 GET /up 返回 200 OK

  10. 把流量切到新容器。

  11. 停掉旧容器,并清理无用镜像和停止的容器。

这里有一个非常重要的小点:**健康检查默认是 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 部署,因为它不需要把完整开发环境原样搬进去。

next.config.js 里可以这样写:

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

module.exports = nextConfig

需要注意的是,Next.js 文档提到,output: 'standalone' 的运行方式更适合直接使用生成的 server.js,而不是继续用 next start

第二步:准备 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、复杂代理配置,会更自然一些。