Docker 部署 Finch
Finch 的设计使其可以很好地在 Docker 中运行。官方镜像 uproid/finch 包含了 Dart SDK、finch CLI,以及构建和运行 Finch 应用所需的一切。本仓库中的 example/ 项目提供了一份完整的 docker-compose.yaml,你可以将其作为起点直接复制使用,其中一站式集成了 MongoDB、MySQL、SQLite、Nginx 反向代理以及 Tailwind 资源构建。
项目结构
一个典型的基于 Docker 的 Finch 项目会使用以下文件:
my-app/
Dockerfile # 构建 Finch 容器镜像
docker-compose.yaml # 编排所有服务
docker/
nginx.conf # Nginx 反向代理配置
lib/ # 应用的 Dart 代码
public/ # 由 Nginx 提供的静态文件
sqlite/ # SQLite 数据库文件(挂载为卷)
mongodb/ # MongoDB 数据文件(挂载为卷)
mysql/ # MySQL 数据文件(挂载为卷)
Dockerfile
示例中的 Dockerfile 使用官方 Finch 基础镜像,并在构建时安装依赖:
FROM uproid/finch:latest AS build
WORKDIR /www
# 路径/行为的默认值——在 docker-compose.yaml 中按环境覆盖
ENV WIDGETS_PATH=./lib/widgets
ENV WIDGETS_TYPE=j2.html
ENV LANGUAGE_PATH=./lib/languages
ENV PUBLIC_DIR=./public
ENV LOCAL_DEBUG=true
ENV ENABLE_DATABASE=true
COPY pubspec.yaml ./
RUN dart pub get --no-offline
COPY lib/ ./lib/
# 构建:生成 finch 应用二进制文件,并预编译路由/模板
RUN finch -u
# 应用端口(8085)和 Dart VM service/DevTools 端口(8181)
EXPOSE 8085 8181
# 启动时:运行应用,并应用待处理的 MySQL 和 SQLite 迁移
CMD ["finch", "serve", "-p", "/www/lib/watcher.dart", "--args=\"migrate --init --and migrate_sqlite --init\""]
finch -u(参见 Finch CLI)会重新激活全局安装的 finch CLI 本身——相当于再次运行 dart pub global activate finch——从而使镜像中的构建/运行工具与最新发布的 Finch 版本保持一致,而不是停留在 uproid/finch:latest 基础镜像当初打包时的版本。CMD 通过 finch serve 运行应用,监听 lib/watcher.dart(参见 Run App),并传入 migrate --init / migrate_sqlite --init,使数据库迁移(参见 Database Migration)在每次容器启动时自动执行——由于已经应用过的迁移会被跳过,因此可以放心地重复运行。
如果你在容器内启用了本地调试器(ENABLE_LOCAL_DEBUGGER=true),还需要 EXPOSE 8282 并在 docker-compose.yaml 中发布该端口——该端口通过 WebSocket 承载调试器的交互式终端,默认情况下不会被暴露。
注意: 本仓库根目录下的
Dockerfile和docker-compose-live.yaml用于构建 Finch 框架 自身,以运行其自带的在线示例,并不适合直接复制到你的应用项目中——请始终以example/Dockerfile和example/docker-compose.yaml作为起点。
docker-compose.yaml
完整的示例技术栈包含五个服务:finch(你的应用)、nginx(反向代理)、mongodb、mysql,以及用于构建 Tailwind CSS 资源的 nodejs:
services:
finch:
hostname: finch
container_name: finch
image: uproid/finch:latest
restart: always
ports:
- "8085:8085" # 应用端口
- "8181:8181" # Dart VM service / DevTools 端口
environment:
SQLITE_PATH: /www/sqlite/example_database.sqlite
ENABLE_DATABASE: true
MONGODB_CONNECTION: mongodb # 使用服务主机名,而不是 localhost
MONGODB_PORT: 27017 # 容器内部端口(不是映射到主机的 27018)
MONGODB_NAME: my_database
MONGODB_AUTH: admin
MONGODB_PASSWORD: change-me
MONGODB_USER: root
MYSQL_HOST: mysql
MYSQL_PORT: 3306
MYSQL_DATABASE: my_database
MYSQL_USER: app_user
MYSQL_PASSWORD: change-me
build:
context: .
volumes:
- ./lib/:/www/lib/ # 热重载源文件,无需重新构建镜像
- ./public/:/www/public/
- ./sqlite/:/www/sqlite/ # 在容器重启之间持久化 SQLite 数据
nodejs:
image: node:20-alpine
container_name: nodejs
working_dir: /app
tty: true
stdin_open: true
volumes:
- ./:/app
command: sh -c "npm install && npm run tailwind:watch"
restart: always
nginx:
image: nginx:latest
container_name: nginx
restart: always
ports:
- "8080:8080" # 对外暴露的端口
volumes:
- ./public/:/var/www/html/public
- ./docker/nginx.conf:/etc/nginx/conf.d/default.conf
mongodb:
image: mongo:latest
container_name: mongodb
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: change-me
ports:
- "27018:27017" # 主机端口使用 27018,以避免与本地安装的 MongoDB 冲突
volumes:
- ./mongodb:/data/db
mysql:
image: mysql:latest
container_name: mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: change-me-root
MYSQL_DATABASE: my_database
MYSQL_USER: app_user
MYSQL_PASSWORD: change-me
ports:
- "3306:3306"
volumes:
- ./mysql:/var/lib/mysql
要点:
nodejs只有在你的项目使用 Tailwind 时才需要(参见示例中的package.json和tailwind.config.js);如果你不需要构建前端资源,可以完全删除该服务。mongodb和mysql是相互独立的——只需在configs.dart中对相应的FinchDBConfig/FinchMysqlConfig/FinchSqliteConfig设置enable: false(参见 Configuration),即可只启用你的应用实际使用的数据库,并直接删除这里对应的未使用服务块。- 在 Docker 网络内部,容器之间是通过服务名(
mongodb、mysql、nginx)互相访问的,而不是通过localhost——这就是为什么MONGODB_CONNECTION和MYSQL_HOST指向的是服务名而不是localhost,尽管本地(非 Docker)开发时使用的同一份.env文件中写的是localhost。 - 映射到主机的 MongoDB 端口(
27018)与 Finch 内部连接使用的端口(27017)是不同的——应用是通过 Docker 网络以其原生端口访问 Mongo 的,而27018的存在只是为了方便你在主机上用本地 MongoDB 客户端连接localhost:27018进行调试。
Nginx 反向代理
docker/nginx.conf 会将常规 HTTP 请求,以及基于 WebSocket 的 /ws 和 /debugger 端点(分别被 WebSockets 和本地调试器使用)一并代理到 finch 容器:
upstream finch {
server finch:8085;
keepalive 32;
}
server {
listen 8080;
server_name localhost:8080;
client_max_body_size 20M;
location / {
root /var/www/html;
try_files /public$uri @dart;
}
# 为 /ws 和 /debugger 路径提供 WebSocket 支持
location ~ ^/(ws|debugger) {
proxy_pass http://finch;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
proxy_set_header Host $host;
proxy_buffering off;
proxy_read_timeout 3600s;
}
location @dart {
proxy_pass http://finch;
proxy_pass_request_body on;
proxy_pass_request_headers on;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
try_files /public$uri @dart 的含义是:当挂载的 public/ 目录中存在对应的静态文件时,Nginx 会直接提供该文件;否则才会将请求转发给 Finch(@dart)——这与在 Docker 之外使用的 Nginx for Finch 配置相同,只是这里指向的是 finch 容器,而不是本地进程。WebSocket location 块中的 proxy_buffering off 以及较长的超时设置是必需的——如果没有它们,Nginx 会缓冲长连接的 WebSocket,或者过早地将其关闭。
启动技术栈
# 构建并启动所有服务
docker-compose up --build
# 在后台运行(分离模式)
docker-compose up -d --build
# 跟踪查看某个服务的日志
docker-compose logs -f finch
# 在 Dockerfile 发生更改后,仅重新构建 finch 服务
docker-compose up -d --build finch
# 停止所有内容(仅容器——卷会被保留)
docker-compose down
# 停止所有内容并删除卷(会清空所有数据库数据)
docker-compose down -v
启动完成后,应用可以通过 http://localhost:8080(经由 Nginx)访问,也可以直接通过 http://localhost:8085(绕过 Nginx)访问——这在排查问题究竟出在 Finch 还是代理配置时很有用。
由于 ./lib/ 和 ./public/ 都以绑定挂载的方式挂载进了容器,在主机上编辑这些文件会被 Finch 的文件监听器立即感知——日常的 Dart 代码更改不需要重新构建镜像,只有当 pubspec.yaml 的依赖发生变化时才需要。
环境变量
每一个从 env.get(...)(或 env.getInt(...) / env.getBool(...))读取的 FinchConfigs 值,都可以设置为 Docker 环境变量,或者通过应用加载的 .env 文件来设置。下面是一份与上方 compose 文件相匹配的最小化 .env:
MONGODB_CONNECTION=mongodb
MONGODB_PORT=27017
MONGODB_NAME=my_database
MONGODB_AUTH=admin
MONGODB_USER=root
MONGODB_PASSWORD=change-me
MYSQL_HOST=mysql
MYSQL_PORT=3306
MYSQL_DATABASE=my_database
MYSQL_USER=app_user
MYSQL_PASSWORD=change-me
SQLITE_PATH=/www/sqlite/example_database.sqlite
ENABLE_LOCAL_DEBUGGER=false
LOCAL_DEBUG=false
完整的受支持环境变量列表,包括此处未展示的服务器、模板和邮件相关设置,请参阅 Configuration。
切勿提交真实密码。 在 docker-compose.yaml 本身中,应优先使用来自已通过 .gitignore 排除的 .env 文件的 ${VAR_NAME} 插值方式,而不是像上面为了简洁而直接在 YAML 中硬编码密钥。
生产环境注意事项
- 在生产环境中设置
ENABLE_LOCAL_DEBUGGER=false和LOCAL_DEBUG=false——本地调试器会向正在运行的容器暴露一个 WebSocket 终端,仅供开发使用。参见 Debugging。 - 不要将 Dart VM service 端口(
8181)暴露到你自己的网络之外;该端口会授予对正在运行的 isolate 的完整读写权限。 - 在整个技术栈前面放置一个真正的 TLS 终止反向代理(带证书的 Nginx,或托管的负载均衡器)——这里的示例
nginx.conf仅支持 HTTP,只是作为一个起点。 - 在生产环境中,为
mongodb/mysql的数据使用具名卷(或托管数据库),而不是绑定挂载主机目录,这样备份和权限管理才能在不同主机之间保持一致。 - 如果并发用户规模超出少数几个,请调高
FinchDBConfig/FinchMysqlConfig上的maxConnections——参见 MongoDB 和 MySQL。