如何使用 Docker 和 Bun 部署 TanStack Start

发布: (2026年1月7日 GMT+8 10:45)
8 min read
原文: Dev.to

Source: Dev.to

TanStack Start 使用 Docker 和 Bun 部署的封面图

rogasper

TanStack Start 正在迅速成为希望获得 TanStack Router 强大功能并具备全栈能力的开发者的首选框架。它前沿、速度快,开发者体验(DX)令人惊叹。

但说实话:从笔记本上使用 npm run dev 迁移到健壮的容器化生产环境,往往是头疼的开始。

在本指南中,我们不仅仅是让它“跑起来”。我们将使用 DockerBun 构建一个 高性能部署流水线。我们会实现一个自定义资源服务器,将文件缓存到 RAM、处理压缩,并以闪电般的速度提供你的应用。

0️⃣ 前置条件(不要跳过!)

在我们动手编辑任何 Dockerfile 之前,有一个关键要求。

根据官方 TanStack Start 文档,使用 Bun 部署目前需要 React 19。如果你的项目仍在使用 React 18,服务器可能会崩溃或表现异常。

请先升级你的依赖:

bun install react@19 react-dom@19

准备好了吗?让我们开始构建。

1️⃣ 基础:Vite 配置

首先,我们需要确保 Vite 已经为生产环境做好准备。我们希望路径别名(如 @/components)能够正常工作,并且去除 console 日志,以保持生产日志的整洁。

vite.config.ts

// vite.config.ts
import { defineConfig } from "vite";
import { devtools } from "@tanstack/devtools-vite";
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import viteReact from "@vitejs/plugin-react";
import viteTsConfigPaths from "vite-tsconfig-paths";
import tailwindcss from "@tailwindcss/vite";
import { fileURLToPath, URL } from "node:url";

export default defineConfig({
  esbuild: {
    // Clean up logs in production
    drop: ["console", "debugger"],
  },
  server: {
    port: 3058,
  },
  plugins: [
    devtools(),
    // This plugin is vital for resolving paths like "@/lib/utils"
    viteTsConfigPaths({
      projects: ["./tsconfig.json"],
    }),
    tailwindcss(),
    tanstackStart(),
    viteReact(),
  ],
  resolve: {
    alias: {
      "@": fileURLToPath(new URL("./src", import.meta.url)),
    },
  },
});

2️⃣ 秘密酱料:自定义 Bun 服务器

这是最酷的部分。我们不再运行 node server.js,而是使用一个利用 Bun 原生速度的自定义脚本。该脚本充当智能资源服务器:它将小文件(图标、CSS、细小的 JS 片段)直接加载到内存(RAM)中,自动进行 Gzip 压缩,并生成 ETag,以实现完美的浏览器缓存。

为什么?

  • 速度 – RAM 比磁盘更快。
  • 压缩 – Gzip 自动处理。
  • 效率 – ETag 让浏览器完美缓存资源。

在项目根目录创建一个名为 server.ts 的文件(请参阅 TanStack Router 仓库中的完整参考实现),并粘贴以下代码:

// server.ts
import path from "node:path";

// --- Configuration ---
const SERVER_PORT = Number(process.env.PORT ?? 3000);
const CLIENT_DIRECTORY = "./dist/client";
const SERVER_ENTRY_POINT = "./dist/server/server.js";

// Simple Logging Utility
const log = {
  info: (msg: string) => console.log(`[INFO] ${msg}`),
  error: (msg: string) => console.error(`[ERROR] ${msg}`),
  success: (msg: string) => console.log(`[SUCCESS] ${msg}`),
};

// --- Asset Preloading Logic ---
// 5 MiB limit for in‑memory files
const MAX_PRELOAD_BYTES = Number(
  process.env.ASSET_PRELOAD_MAX_SIZE ?? 5 * 1024 * 1024
);

// (Helper functions for ETag, Gzip, and Glob patterns omitted for brevity
// but ensure you include the full logic from the source provided earlier!)

/**
 * The Main Server Initializer
 */
async function initializeServer() {
  log.info("Starting Production Server...");

  // 1. Load the TanStack Start handler
  let handler: { fetch: (request: Request) => Response | Promise };
  try {
    const serverModule = (await import(SERVER_ENTRY_POINT)) as any;
    handler = serverModule.default;
    log.success("TanStack Start handler initialized");
  } catch (error) {
    log.error(`Failed to load handler: ${String(error)}`);
    process.exit(1);
  }

  // 2. Initialise static routes (scans ./dist/client)
  // Note: In your full file, ensure you include the `initializeStaticRoutes` function!
  const { routes } = await initializeStaticRoutes(CLIENT_DIRECTORY);

  // 3. Start Bun server
  const server = Bun.serve({
    port: SERVER_PORT,
    routes: {
      // Serve cached assets first
      ...routes,

      // Fallback to the App handler for everything else
      "/*": (req: Request) => {
        return handler.fetch(req);
      },
    },
  });

  log.success(`Server listening on http://localhost:${String(server.port)}`);
}

initializeServer().catch((err) => {
  log.error(String(err));
});

// ... (Paste the rest of the helper functions: initializeStaticRoutes, etc. here)

重要提示: 使用官方材料中的完整 server.ts 代码,以获得完整的缓存优势。

3️⃣ Dockerfile:小巧高效

我们将使用 多阶段构建 来保持最终镜像体积极小。(Dockerfile 的其余内容遵循相同的模式——复制构建好的资源,仅安装生产依赖,并运行 Bun 服务器。)

# ---- Builder Stage ----
FROM oven/bun:1 AS builder

WORKDIR /app

# Install dependencies (including React 19)
COPY package.json bun.lockb ./
RUN bun install --production=false

# Copy source files
COPY . .

# Build the client and server bundles
RUN bun run build

# ---- Runtime Stage ----
FROM oven/bun:1-alpine AS runtime

WORKDIR /app

# Copy only the built output and production deps
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json .
COPY --from=builder /app/bun.lockb .

# Install only production dependencies
RUN bun install --production

# Expose the port the server listens on
EXPOSE 3000

# Start the Bun server
CMD ["bun", "run", "server.ts"]

为什么这个 Docker 设置有效

我们保持最终的 Docker 镜像体积小,因为我们不把构建工具(TypeScript、Vite 等)放入生产容器——只包含运行应用所需的内容。

关键技术细节

Vite 需要在构建时获取环境变量(例如你的 API URL),以便将它们“烘焙”进客户端 JavaScript。请特别注意 Dockerfile 中的 ARG 部分。

Dockerfile(多阶段)

# syntax=docker/dockerfile:1

# =============================================
# Stage 1: Builder
# =============================================
FROM oven/bun:1-alpine AS builder
WORKDIR /app

# Install dependencies (cached layer)
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile

# Copy the source code
COPY . .

# --- IMPORTANT ---
# Vite needs these variables AT BUILD TIME to replace import.meta.env values
ARG VITE_API_URL
ARG VITE_BETTER_AUTH_URL
ENV VITE_API_URL=${VITE_API_URL}
ENV VITE_BETTER_AUTH_URL=${VITE_BETTER_AUTH_URL}

# Build the app
RUN bun run build

# =============================================
# Stage 2: Runner ( Production )
# =============================================
FROM oven/bun:1-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production

# Install curl for healthchecks
RUN apk add --no-cache curl

# Copy package files
COPY package.json bun.lock ./

# Install ONLY production deps (react, etc.)
RUN bun install --production

# Copy the build artifacts from the Builder stage
COPY --from=builder --chown=bun:bun /app/dist ./dist
COPY --from=builder --chown=bun:bun /app/server.ts ./server.ts

USER bun

# The port is dynamic via Env Vars
ENV PORT=3058
EXPOSE 3058

# Start the custom Bun server
CMD ["bun", "server.ts"]

4️⃣ 编排:Docker Compose

使用 Docker Compose 可以轻松管理环境变量、端口和健康检查。

version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        # Pass these to the Builder stage!
        VITE_BETTER_AUTH_URL: ${VITE_BETTER_AUTH_URL}
        VITE_API_URL: ${VITE_API_URL}   # (optional, if needed at build time)

    environment:
      # Runtime config for the server
      NODE_ENV: production
      PORT: ${PORT:-3058}

      # App secrets & configs
      VITE_API_URL: ${VITE_API_URL}
      DATABASE_URL: ${DATABASE_URL}
      BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}

      # SMTP / Email config
      HOST_EMAIL: ${HOST_EMAIL}
      HOST_PORT: ${HOST_PORT}
      HOST_AUTH_USER: ${HOST_AUTH_USER}
      HOST_AUTH_PASS: ${HOST_AUTH_PASS}

    ports:
      # Map host port to container port
      - '${PORT:-3058}:${PORT:-3058}'

    restart: always

    healthcheck:
      # Ping the server to ensure it's alive
      test: ['CMD-SHELL', 'curl -fsS http://localhost:${PORT:-3058} || exit 1']
      interval: 10s
      timeout: 5s
      retries: 5

结论

您不再在容器内部运行开发服务器。此设置为您提供了一个 高度优化、内存缓存、基于 Bun 的生产环境

快速回顾

  • React 19 – Bun 开发的必备版本
  • server.ts – 提供高级缓存和 gzip 压缩
  • Dockerfile – 多阶段构建,正确处理构建时环境变量
  • Docker Compose – 通过健康检查编排运行时
  1. 创建一个包含所需变量的 .env 文件。
  2. 运行:
docker compose up --build -d
  1. 观看您的 TanStack Start 应用飞速运行!

欲了解更多详情,请访问我的 blog

Back to Blog

相关文章

阅读更多 »