Skip to content

部署到生产环境

¥Deploy to production

本页是如何将 Elysia 部署到生产的指南。

¥This page is a guide on how to deploy Elysia to production.

编译为二进制文件

¥Compile to binary

我们建议在部署到生产环境之前运行构建命令,因为它可以显著减少内存使用量和文件大小。

¥We recommend running a build command before deploying to production as it could potentially reduce memory usage and file size significantly.

我们建议使用以下命令将 Elysia 编译为单个二进制文件:

¥We recommend compiling Elysia into a single binary using the command as follows:

bash
bun build \
	--compile \
	--minify-whitespace \
	--minify-syntax \
	--outfile server \
	src/index.ts

这将生成一个可移植的二进制文件 server,我们可以运行它来启动服务器。

¥This will generate a portable binary server which we can run to start our server.

与开发环境相比,将服务器编译为二进制文件通常可以显著减少 2-3 倍的内存使用量。

¥Compiling server to binary usually significantly reduces memory usage by 2-3x compared to development environment.

这条命令有点长,我们来分解一下:

¥This command is a bit long, so let's break it down:

  1. --compile 将 TypeScript 编译为二进制文件
  2. --minify-whitespace 删除不必要的空格
  3. --minify-syntax 压缩 JavaScript 语法以减小文件大小
  4. --outfile server 将二进制文件输出为 server
  5. src/index.ts 我们服务器的入口文件(代码库)

要启动服务器,只需运行二进制文件即可。

¥To start our server, simply run the binary.

bash
./server

编译二进制文件后,你无需在计算机上安装 Bun 即可运行服务器。

¥Once binary is compiled, you don't need Bun installed on the machine to run the server.

这非常棒,因为部署服务器无需安装额外的运行时即可运行,从而使二进制文件可移植。

¥This is great as the deployment server doesn't need to install an extra runtime to run making binary portable.

目标

¥Target

你还可以添加 --target 标志,以针对目标平台优化二进制文件。

¥You can also add a --target flag to optimize the binary for the target platform.

bash
bun build \
	--compile \
	--minify-whitespace \
	--minify-syntax \
	--target bun-linux-x64 \
	--outfile server \
	src/index.ts

以下是可用目标列表:

¥Here's a list of available targets:

目标操作系统架构现代基线Libc
bun-linux-x64Linuxx64glibc
bun-linux-arm64Linuxarm64N/Aglibc
bun-windows-x64Windowsx64*
bun-windows-arm64Windowsarm64*
bun-darwin-x64macOSx64*
bun-darwin-arm64macOSarm64N/A*
bun-linux-x64-muslLinuxx64musl
bun-linux-arm64-muslLinuxarm64N/Amusl

为什么不使用 --minify

¥Why not --minify

Bun 确实有 --minify 标志可以最小化二进制文件。

¥Bun does have --minify flag that will minify the binary.

但是,如果我们使用 OpenTelemetry,它会将函数名称缩减为一个字符。

¥However if we are using OpenTelemetry, it's going to reduce a function name to a single character.

由于 OpenTelemetry 依赖于函数名,这使得跟踪变得比应有的更困难。

¥This makes tracing harder than it should as OpenTelemetry relies on a function name.

但是,如果你不使用 OpenTelemetry,你可以选择使用 --minify

¥However, if you're not using OpenTelemetry, you may opt in for --minify instead

bash
bun build \
	--compile \
	--minify \
	--outfile server \
	src/index.ts

权限

¥Permission

某些 Linux 发行版可能无法运行该二进制文件,如果你使用的是 Linux,我们建议你启用二进制文件的可执行权限:

¥Some Linux distros might not be able to run the binary, we suggest enabling executable permission to a binary if you're on Linux:

bash
chmod +x ./server

./server

未知随机中文错误

¥Unknown random Chinese error

如果你尝试将二进制文件部署到服务器,但无法运行并出现随机中文错误。

¥If you're trying to deploy a binary to your server but unable to run with random chinese character error.

这意味着你正在运行的机器不支持 AVX2。

¥It means that the machine you're running on doesn't support AVX2.

遗憾的是,Bun 需要支持 AVX2 硬件的机器。

¥Unfortunately, Bun requires a machine that has AVX2 hardware support.

据我们所知,没有其他解决方法。

¥There's no workaround as far as we know.

编译为 JavaScript

¥Compile to JavaScript

如果你无法编译为二进制文件或你正在 Windows 服务器上部署。

¥If you are unable to compile to binary or you are deploying on a Windows server.

你也可以将服务器打包到 JavaScript 文件中。

¥You may bundle your server to a JavaScript file instead.

bash
bun build \
	--minify-whitespace \
	--minify-syntax \
	--outfile ./dist/index.js \
	src/index.ts

这将生成一个可移植的 JavaScript 文件,你可以将其部署到你的服务器上。

¥This will generate a single portable JavaScript file that you can deploy on your server.

bash
NODE_ENV=production bun ./dist/index.js

Docker

在 Docker 上,我们建议始终编译为二进制文件以减少基础镜像的开销。

¥On Docker, we recommended to always compile to binary to reduce base image overhead.

以下是使用 Distroless 二进制镜像的示例镜像。

¥Here's an example image using Distroless image using binary.

dockerfile
FROM oven/bun AS build

WORKDIR /app

# Cache packages installation
COPY package.json package.json
COPY bun.lock bun.lock

RUN bun install

COPY ./src ./src

ENV NODE_ENV=production

RUN bun build \
	--compile \
	--minify-whitespace \
	--minify-syntax \
	--outfile server \
	src/index.ts

FROM gcr.io/distroless/base

WORKDIR /app

COPY --from=build /app/server server

ENV NODE_ENV=production

CMD ["./server"]

EXPOSE 3000

OpenTelemetry

如果你使用 OpenTelemetry 部署生产服务器。

¥If you are using OpenTelemetry to deploys production server.

由于 OpenTelemetry 依赖于 monkey-patching node_modules/<library>。为了使 make instrumentations 正常工作,我们需要将要被 instrument 的库指定为外部模块,以将其排除在打包之外。

¥As OpenTelemetry rely on monkey-patching node_modules/<library>. It's required that make instrumentations works properly, we need to specify that libraries to be instrument is an external module to exclude it from being bundled.

例如,如果你使用 @opentelemetry/instrumentation-pg 来检测 pg 库。我们需要将 pg 从打包包中排除,并确保它导入 node_modules/pg

¥For example, if you are using @opentelemetry/instrumentation-pg to instrument pg library. We need to exclude pg from being bundled and make sure that it is importing node_modules/pg.

为了使其正常工作,我们可以使用 --external pgpg 指定为外部模块。

¥To make this works, we may specified pg as an external module with --external pg

bash
bun build --compile --external pg --outfile server src/index.ts

这告诉 bun 不要将 pg 打包到最终输出文件中,而是在运行时从 node_modules 目录导入。所以,在生产服务器上,你还必须保留 node_modules 目录。

¥This tells bun to not pg bundled into the final output file, and will be imported from the node_modules directory at runtime. So on a production server, you must also keeps the node_modules directory.

建议在 package.json 中将生产服务器中应可用的软件包指定为 dependencies,并使用 bun install --production 仅安装生产依赖。

¥It's recommended to specify packages that should be available in a production server as dependencies in package.json and use bun install --production to install only production dependencies.

json
{
	"dependencies": {
		"pg": "^8.15.6"
	},
	"devDependencies": {
		"@elysiajs/opentelemetry": "^1.2.0",
		"@opentelemetry/instrumentation-pg": "^0.52.0",
		"@types/pg": "^8.11.14",
		"elysia": "^1.2.25"
	}
}

然后在生产服务器上运行构建命令后,

¥Then after running a build command, on a production server

bash
bun install --production

如果 node_modules 目录仍然包含开发依赖,你可以删除 node_modules 目录并重新安装生产依赖。

¥If the node_modules directory still includes development dependencies, you may remove the node_modules directory and reinstall production dependencies again.

Monorepo

如果你使用 Elysia 和 Monorepo,则可能需要包含依赖 packages

¥If you are using Elysia with Monorepo, you may need to include dependent packages.

如果你使用 Turborepo,则需要将 Dockerfile 放在你的应用目录中,例如 apps/server/Dockerfile。这可能适用于其他 monorepo 管理器,例如 Lerna 等。

¥If you are using Turborepo, you may place a Dockerfile inside an your apps directory like apps/server/Dockerfile. This may apply to other monorepo manager such as Lerna, etc.

假设我们的 monorepo 使用的是 Turborepo,其结构如下:

¥Assume that our monorepo are using Turborepo with structure as follows:

  • apps

    • server

      • Dockerfile(在此处放置 Dockerfile)
  • packages

    • config

然后我们可以在 monorepo 根目录(而不是应用根目录)上构建 Dockerfile:

¥Then we can build our Dockerfile on monorepo root (not app root):

bash
docker build -t elysia-mono .

Dockerfile 如下:

¥With Dockerfile as follows:

dockerfile
FROM oven/bun:1 AS build

WORKDIR /app

# Cache packages
COPY package.json package.json
COPY bun.lock bun.lock

COPY /apps/server/package.json ./apps/server/package.json
COPY /packages/config/package.json ./packages/config/package.json

RUN bun install

COPY /apps/server ./apps/server
COPY /packages/config ./packages/config

ENV NODE_ENV=production

RUN bun build \
	--compile \
	--minify-whitespace \
	--minify-syntax \
	--outfile server \
	src/index.ts

FROM gcr.io/distroless/base

WORKDIR /app

COPY --from=build /app/server server

ENV NODE_ENV=production

CMD ["./server"]

EXPOSE 3000

Railway

Railway 是流行的部署平台之一。

¥Railway is one of the popular deployment platform.

Railway 为每个部署分配一个随机端口,可通过 PORT 环境变量访问。

¥Railway assigns a random port to expose for each deployment, which can be accessed via the PORT environment variable.

我们需要修改 Elysia 服务器以接受 PORT 环境变量,从而符合 Railway 端口的要求。

¥We need to modify our Elysia server to accept the PORT environment variable to comply with Railway port.

我们可以使用 process.env.PORT 而不是固定端口,并在开发过程中提供回退。

¥Instead of a fixed port, we may use process.env.PORT and provide a fallback on development instead.

ts
new Elysia()
	.listen(3000) 
	.listen(process.env.PORT ?? 3000) 

这应该允许 Elysia 拦截 Railway 提供的端口。

¥This should allows Elysia to intercept port provided by Railway.

提示

Elysia 会自动将主机名分配给 0.0.0.0,这与 Railway 兼容。

¥Elysia assign hostname to 0.0.0.0 automatically, which works with Railway