容器化时代,部署web项目早已不需要购买一台完整的服务器。

我们只需要将程序打包为docker镜像并上传docker hub,容器托管平台(claw run,sealos,zeabur等)便可以通过k8s运行我们的程序,并高度细致分配cpu,内存,硬盘,网络等资源,需要多少用多少,按量付费,相比于购买一台完整的服务器成本更低的同时,也省去了部署的烦琐。

golang作为一门编译型语言,同时可以完全不依赖libc,使得其构建docker镜像时拥有得天独厚的优势。

Dockerfile

这是llmio的DockerFile文件,相比于其他语言可以说是十分简单清晰了。

llmio所使用的数据库是纯go实现的sqlite,没有任何C依赖。

因为需要备份导出sqlite的db文件,需要镜像有一个文件系统,所以我没有使用scratch,而是alpine。

 1# Build stage for the frontend
 2FROM node:20 AS frontend-build
 3WORKDIR /app
 4COPY webui/package.json webui/pnpm-lock.yaml ./
 5RUN npm install -g pnpm
 6RUN pnpm install
 7COPY webui/ .
 8RUN pnpm run build
 9
10# Build stage for the backend
11FROM golang:latest AS backend-build
12WORKDIR /app
13COPY go.mod go.sum ./
14RUN GOPROXY=https://goproxy.io,direct go mod download
15COPY . .
16RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o llmio .
17
18# Final stage
19FROM alpine:latest
20
21WORKDIR /app/db
22WORKDIR /app
23
24# Copy the binary from backend build stage
25COPY --from=backend-build /app/llmio .
26
27# Copy the built frontend from frontend build stage
28COPY --from=frontend-build /app/dist ./webui/dist
29
30EXPOSE 7070
31
32# Command to run the application
33CMD ["./llmio"]

时区与TLS证书

当程序需要设置时区或者对外发出https请求时,很多语言会调用操作系统提供的时区配置或openssl,这同样加入了不必要的依赖,然而golang中完全无需担心这些内容,只需要引入两个包即可将两种依赖打包到可执行文件中。

 1import (
 2    _ "time/tzdata"
 3    _ "golang.org/x/crypto/x509roots/fallback"
 4)
 5// 根据环境变量设置时区
 6func init() {
 7	models.Init("./db/llmio.db")
 8	location, err := time.LoadLocation(os.Getenv("TZ"))
 9	if err != nil {
10		slog.Warn("failed to load timezone, location reset to UTC", "error", err)
11		location = time.UTC
12	}
13	_ = location
14}

github action自动构建

受限于中国大陆地区gfw网络环境,在本地构建并推送往往是一件麻烦事,所以可以使用github action自动帮我们构建多平台镜像并推送到docker hub.

 1name: ci build and push docker image
 2
 3on:
 4  push:
 5    tags:
 6      - 'v*.*.*'
 7
 8# 1. 设置动态的镜像名称
 9env:
10  # 自动构建镜像名称,格式为:<dockerhub-username>/<repo-name>
11  # 例如:'my-docker-user/my-awesome-app'
12  IMAGE_NAME: ${{ github.repository }}
13
14jobs:
15  docker:
16    runs-on: ubuntu-latest
17    steps:
18      - name: Checkout repository
19        uses: actions/checkout@v4
20
21      # 2. 动态生成 Docker 标签 (version 和 latest)
22      - name: Extract metadata (tags, labels) for Docker
23        id: meta
24        uses: docker/metadata-action@v5
25        with:
26          images: ${{ env.IMAGE_NAME }} # 使用我们动态生成的镜像名
27          tags: |
28            type=semver,pattern={{version}}
29            type=raw,value=latest
30
31      - name: Login to Docker Hub
32        uses: docker/login-action@v3
33        with:
34          username: ${{ github.repository_owner }}
35          password: ${{ secrets.DOCKERHUB_TOKEN }}
36
37      - name: Set up QEMU
38        uses: docker/setup-qemu-action@v3
39
40      - name: Set up Docker Buildx
41        uses: docker/setup-buildx-action@v3
42
43      # 3. 构建并推送最终镜像 (使用 meta 步骤生成的动态标签)
44      - name: Build and push final image
45        if: success() # 确保测试通过后才执行
46        uses: docker/build-push-action@v6
47        with:
48          context: .
49          platforms: linux/amd64,linux/arm64
50          push: true
51          tags: ${{ steps.meta.outputs.tags }}
52          labels: ${{ steps.meta.outputs.labels }}

低成本部署

现在一个零依赖的docker镜像已经打包好了,只需要找一家容器托管平台即可部署,这里我选择sealos。 alt text 得益于golang程序的低内存占用,我们可以将资源拉到最低,每天仅需8分钱!成本遥遥领先于传统服务器部署。