1. 为什么是 azk 而不是 Docker Compose一个被低估的 Ruby 应用编排工具你可能刚在终端里敲完docker-compose up -d看着一堆容器启动成功心里松了口气——但五分钟后bundle install在 production 镜像里报错说找不到libpq-dev再过十分钟发现RAILS_ENVproduction rails db:migrate死在了ActiveRecord::ConnectionNotEstablished上最后你翻了三页 Stack Overflow才意识到database.yml里写的host: db在 Docker Compose 里确实能通但在 azk 的默认网络模型下服务名解析规则根本不一样。这不是你的错。这是绝大多数 Rails 开发者第一次接触 azk 时踩进的第一个认知陷阱azk 不是 Docker Compose 的平替而是一套为 Ruby 生态深度定制的、带状态感知的本地开发与部署协同系统。它诞生于 2014 年巴西一家专注 Ruby 服务的创业公司核心目标很朴素让一个刚 clone 下来的 Rails 项目执行一条命令就能跑起来且这个“跑起来”的环境和最终部署到 staging 或 production 的配置结构完全对齐——不是相似是结构级一致。我第一次在客户现场用 azk 部署一个 Rails 5.2 PostgreSQL Redis Sidekiq 的电商后台时整个过程从初始化到可访问只用了 11 分钟。关键不在于快而在于所有环节都可复现、可审计、可回滚。azk 把Dockerfile、docker-compose.yml、.env、secrets.yml、甚至capistrano的 deploy.rb 片段全部收束进一个叫Azkfile.js的单文件里。这个文件不是配置清单而是一个可执行的 JavaScript 模块——你可以写条件判断、动态拼接 env 变量、根据 NODE_ENV 注入不同中间件、甚至调用 shell 命令做前置校验。这种能力在 2024 年看依然比多数 YAML 驱动的工具更贴近开发者的真实工作流。提示azk 的核心价值不在“容器化”而在“环境契约化”。它强制你把“这个 Rails App 跑起来需要什么”定义成一份带执行逻辑的契约而不是一堆零散的配置片段。这直接决定了后续部署的稳定性和协作效率。关键词Rails、azk、Rails App、Deploy并非简单并列而是构成了一条隐含的技术链路Rails是应用层框架azk是环境抽象层Rails App是交付物实体Deploy是动作终点。脱离其中任一环谈部署都会掉进“本地能跑线上炸锅”的经典陷阱。而linux deploy 操作环境更新错误这个热搜词恰恰暴露了当前最普遍的断点——很多人以为部署失败是因为 Linux 系统版本太新或太旧其实 90% 的 case 是因为deploy动作没有绑定到一个明确、可验证、可迁移的环境契约上。azk 就是来补上这一环的。我见过太多团队在 CI/CD 流水线里硬塞docker builddocker pushssh into server docker pull docker run的三段式脚本结果每次 Ubuntu 升级内核或者 OpenSSL 更新小版本就触发一次全站 SSL 握手失败。问题从来不在 Linux 本身而在于他们从未定义过“这个 Rails App 所依赖的 OpenSSL 版本边界是多少”。azk 的image字段支持精确指定基础镜像标签如ruby:3.1.4-slim-bookwormprovision钩子允许你在容器启动前执行apt-get update apt-get install -y libxml2-dev而这一切都被固化在Azkfile.js中成为可 git commit、可 code review、可 diff 对比的代码资产。所以这篇内容不是教你“如何用 azk 部署 Rails”而是带你重建一套关于“Ruby 应用环境可信交付”的认知框架。接下来的每一步操作背后都有明确的设计意图和取舍逻辑。我们不跳过任何看似琐碎的细节因为正是这些细节决定了你明天凌晨三点会不会被 PagerDuty 的告警电话吵醒。2. Azkfile.js 的骨架解剖从空文件到可运行契约的七步构建法Azkfile.js是 azk 的心脏但它绝不是.dockerignore那种声明式清单。它是一个 Node.js 模块导出一个systems对象每个 key 是一个服务名如rails_appvalue 是该服务的完整定义。很多初学者卡在第一步新建一个空文件然后就不知道往哪写了。下面我用一个真实项目Rails 7.1 PostgreSQL 15 Redis 7为例手把手拆解从零开始构建Azkfile.js的完整路径每一步都解释“为什么必须这样写”。2.1 第一步定义主系统入口systems.rails_appsystems: { rails_app: { // 这里开始填内容 } }注意命名规范rails_app是服务名将用于 azk 内部 DNS 解析如http://rails_app.azk.dev也作为容器 hostname。不要用my-rails-app这类带连字符的名字azk 的 DNS 解析器对 hostname 有严格限制只接受字母、数字和下划线。2.2 第二步选择精准的基础镜像image字段rails_app: { image: { docker: ruby:3.1.4-slim-bookworm } }这里必须强调三个关键点版本锁定3.1.4而非3.1或latest。Ruby 官方镜像的slim版本基于 Debianbookworm是其代号。slim-bookworm比alpine更兼容 Rails 生态尤其nokogiri编译比buster旧版 Debian更安全。我实测过ruby:3.1-alpine3.18在bundle install时会因 musl libc 缺失libffi头文件而失败而slim-bookworm一次性通过。slim而非fullfull镜像包含大量开发工具gcc, make, autoconf体积超 1GB部署时浪费带宽且增加攻击面。slim已预装build-essential所需的核心组件足够编译 native extensions。docker作为 providerazk 支持多种 provider如vagrant,vmware但生产部署只认docker。显式声明避免未来误配。2.3 第三步声明依赖服务depends字段rails_app: { image: { docker: ruby:3.1.4-slim-bookworm }, depends: [postgres, redis] }depends不是简单的启动顺序控制。azk 会为每个依赖项自动注入环境变量如POSTGRES_HOSTpostgres,REDIS_URLredis://redis:6379/0并确保rails_app容器的/etc/hosts文件中已写入postgres和redis的 IP 映射。这比 Docker Compose 的links更底层、更可靠。我曾遇到一个 caseDocker Compose 的depends_on只检查容器是否started但 PostgreSQL 实际还没 ready 接受连接导致 Rails 启动失败而 azk 的depends会配合健康检查见后文health_check真正等待服务可用。2.4 第四步挂载源码与工作目录mounts字段rails_app: { image: { docker: ruby:3.1.4-slim-bookworm }, depends: [postgres, redis], mounts: { /azk/bundler: persistent(bundler), /azk/rails_app: path(.), /azk/rails_app/tmp: transient(), } }这里藏着三个易错点persistent(bundler)创建一个名为bundler的持久卷挂载到容器内/azk/bundler。这是为了缓存bundle install生成的 gems避免每次重启都重装。persistent()是 azk 的 DSL 函数参数是卷名它会自动在宿主机~/.azk/volumes/下创建对应目录。path(.)将当前项目根目录即Azkfile.js所在目录挂载为/azk/rails_app。注意不是./path()是 azk 的路径解析函数确保跨平台兼容。transient()为tmp目录创建临时卷保证每次启动都是干净的临时文件空间避免tmp/cache积累脏数据影响测试。2.5 第五步配置环境变量envs字段rails_app: { // ... 其他字段 envs: { RAILS_ENV: development, RACK_ENV: development, DATABASE_URL: postgresql://postgres:postgrespostgres:5432/rails_app_development, REDIS_URL: redis://redis:6379/0, BUNDLE_PATH: /azk/bundler, } }重点看DATABASE_URLpostgres:postgrespostgres:5432中第一个postgres是数据库用户名第二个postgres是密码第三个postgres是服务名由depends定义5432是 PostgreSQL 默认端口。这个 URL 必须和postgres服务的envs.POSTGRES_PASSWORD严格一致。我踩过的坑是postgres服务里设了POSTGRES_PASSWORDmy_secret但rails_app的DATABASE_URL还写着postgres:postgres结果 Rails 启动时报FATAL: password authentication failed for user postgres。azk 不会帮你做密码同步这是契约的一部分必须手动对齐。2.6 第六步定义健康检查health_check字段rails_app: { // ... 其他字段 health_check: { url: http://localhost:3000/health, timeout: 3000, interval: 5000, } }health_check是 azk 区别于其他工具的关键能力。它不是一个简单的curl -f http://localhost:3000而是内置了一个轻量 HTTP 客户端会持续轮询指定 URL直到返回 HTTP 2xx 状态码才认为服务 ready。timeout是单次请求超时毫秒interval是重试间隔。这个机制让azk start命令可以真正“阻塞等待”而不是盲目启动后就返回。我在部署一个带大量ActiveRecord初始化逻辑的 Rails App 时发现rails server进程虽然起来了但ActiveRecord::Base.connection要等 8 秒才真正连上 DB。没有health_check前端流量就会打到一个“半死不活”的实例上造成雪崩。加上后azk 会耐心等到第 9 秒/health返回 200才宣告服务就绪。2.7 第七步定义启动命令command字段rails_app: { // ... 其他字段 command: bash -c bundle install bundle exec rails s -p 3000 -b 0.0.0.0, }command是容器启动后执行的 shell 命令。这里有两个深意bundle install必须放在command里而不是写在Dockerfile的RUN指令中。因为mounts把源码挂载进来了Gemfile可能随时修改bundle install必须在运行时执行才能反映最新依赖。-b 0.0.0.0是关键Rails 默认绑定127.0.0.1在容器里只能被 localhost 访问外部无法连入。-b 0.0.0.0绑定到所有接口让 azk 的反向代理azk agent能转发请求。漏掉这个你会看到azk status显示服务 running但浏览器打不开http://rails_app.azk.dev。至此一个最小可行的Azkfile.js就完成了。它不是一个静态配置而是一个可执行契约当你运行azk start rails_appazk 会按顺序拉取镜像、创建网络、启动依赖、挂载卷、注入环境、执行命令、等待健康检查通过。整个过程就是一次对契约的严格履约。3. 依赖服务的精准配置PostgreSQL 与 Redis 的生产级参数调优很多团队把Azkfile.js里的postgres和redis服务当成“开箱即用”的黑盒只改改密码就完事。结果上线后PostgreSQL 因shared_buffers设置过小导致查询慢 3 倍Redis 因maxmemory未设而 OOM 被系统 kill。azk 的强大之处在于它让你能把生产环境的数据库调优参数以代码形式直接写进Azkfile.js实现真正的“开发即生产”。3.1 PostgreSQL 服务从默认配置到高并发就绪postgres: { image: { docker: postgres:15.3 }, restart: true, wait: 30000, mounts: { /var/lib/postgresql/data: persistent(postgres_data), }, envs: { POSTGRES_USER: postgres, POSTGRES_PASSWORD: postgres, POSTGRES_DB: rails_app_development, POSTGRES_INITDB_ARGS: --auth-hostmd5 --auth-localmd5, }, ports: { 5432: 5432, }, scalable: { default: 1 }, health_check: { url: tcp://localhost:5432, timeout: 5000, interval: 10000, } }逐项解析关键参数image: postgres:15.3必须锁定小版本。PostgreSQL 15.2 和 15.3 在 WAL 日志处理上有细微差异可能导致主从同步异常。15是大版本15.3是具体 patch 版本确保团队所有成员使用完全一致的数据库行为。POSTGRES_INITDB_ARGS: --auth-hostmd5 --auth-localmd5这是安全底线。默认的postgres镜像在local连接Unix socket时使用peer认证意味着只要你是系统用户postgres就能无密码登录。这在本地开发无所谓但一旦Azkfile.js被误提交到 CI 环境就等于开了后门。--auth-localmd5强制本地连接也需密码和--auth-hostmd5保持一致。ports: { 5432: 5432 }格式是容器内端口: 宿主机端口。这里5432:5432表示将容器的 5432 端口映射到宿主机的 5432 端口。这样你可以在宿主机用psql -h localhost -U postgres直连方便调试。但注意生产部署时ports字段应删除因为外部流量不应直连数据库只应通过 Rails App 访问。health_check: { url: tcp://localhost:5432 }PostgreSQL 没有 HTTP 接口所以健康检查用tcp协议。azk 会尝试建立 TCP 连接到localhost:5432如果连接成功即端口开放、服务监听就认为 PostgreSQL ready。timeout设为 5000msinterval10000ms给 PostgreSQL 充足的启动时间初始化 shared memory 等。注意wait: 30000是 azk 的老式等待机制已被health_check取代。但为了兼容旧版本 azk建议保留并设为和health_check.timeout一致的值30000ms。新版本 azk 会优先使用health_check。3.2 Redis 服务内存策略与持久化的平衡术redis: { image: { docker: redis:7.2-alpine }, restart: true, mounts: { /data: persistent(redis_data), }, envs: { REDIS_PASSWORD: redis_password, }, ports: { 6379: 6379, }, scalable: { default: 1 }, health_check: { url: tcp://localhost:6379, timeout: 3000, interval: 5000, }, provision: [ redis-cli -a $REDIS_PASSWORD CONFIG SET maxmemory 256mb, redis-cli -a $REDIS_PASSWORD CONFIG SET maxmemory-policy allkeys-lru, ] }Redis 的provision字段是 azk 的杀手锏。它允许你在容器启动后、服务正式提供服务前执行任意 shell 命令。这里我们做了两件事CONFIG SET maxmemory 256mb强制设置 Redis 最大内存为 256MB。这是防止 Redis 无节制吃光宿主机内存的保险丝。如果不设Redis 默认使用noeviction策略当内存满时直接拒绝写入导致 Rails 的Rails.cache.write报错。256MB 是一个经验值适用于中小型 Rails App 的 session store 和 fragment cache。你可以根据RAILS_ENVproduction rails console里执行Rails.cache.stats查看实际内存占用再调整。CONFIG SET maxmemory-policy allkeys-lru当内存达到 256MB 时采用 LRU最近最少使用算法淘汰 key。allkeys-lru比volatile-lru只淘汰设置了过期时间的 key更稳妥因为 Rails 的cache方法默认不设 TTL全靠allkeys-lru来兜底。provision的执行时机非常关键它在容器ENTRYPOINT启动 Redis 进程之后但在health_check开始之前。这意味着redis-cli命令一定能连上正在运行的 Redis 实例。我曾在一个项目里把provision写成redis-server /usr/local/etc/redis.conf 结果redis-cli连不上因为启动的是后台进程provision脚本执行完就退出了Redis 进程反而被杀。正确做法永远是让provision去配置已经 running 的服务而不是去启动它。3.3 为什么不用docker-compose.yml一个对比表格揭示本质差异维度Docker Compose (docker-compose.yml)azk (Azkfile.js)配置语言YAML声明式无逻辑JavaScript命令式可编程环境变量注入仅通过environment字段静态注入自动注入SERVICE_NAME_HOST、SERVICE_NAME_PORT等且支持模板语法${env.SOME_VAR}依赖启动depends_on仅检查容器状态不检查服务 readinessdependshealth_check真正等待服务可连接持久化存储需额外定义volumes块语法冗长persistent(name)一行搞定自动管理宿主机路径运行时配置无法在容器启动后执行命令除非改写entrypointprovision字段原生支持启动后配置多环境支持需要docker-compose.override.yml或--env-file可在Azkfile.js中用if (process.env.NODE_ENV production) { ... }动态分支这个表格不是为了贬低 Docker Compose而是说明当你需要的不只是“启动一堆容器”而是“确保一个 Ruby 应用在可控、可验证、可复现的环境中稳定运行”azk 提供的抽象层级更贴合 Rails 开发者的思维模型。它把运维的“状态管理”逻辑封装进了开发者熟悉的 JavaScript 语法里。4. 从本地开发到生产部署azk 的三层环境契约落地实践很多团队把 azk 当成纯本地开发工具认为“部署”还得靠 Capistrano 或 Ansible。这是对 azk 最大的误解。azk 的设计哲学是开发、测试、预发布、生产应该共享同一份环境契约只是执行上下文不同。Azkfile.js不是开发专用配置而是整个应用生命周期的环境蓝图。下面我以一个真实 SaaS 项目的演进为例展示如何用同一份Azkfile.js支撑从azk start到azk deploy的全流程。4.1 第一层本地开发环境azk start这是最常用场景。执行azk start rails_appazk 会解析Azkfile.js确认rails_app依赖postgres和redis拉取ruby:3.1.4-slim-bookworm、postgres:15.3、redis:7.2-alpine镜像创建azk网络分配子网如172.18.0.0/16启动postgres容器挂载persistent(postgres_data)注入POSTGRES_PASSWORD启动redis容器挂载persistent(redis_data)执行provision命令启动rails_app容器挂载源码、注入DATABASE_URL、执行bundle install rails s等待rails_app.health_check.url返回 200宣告就绪此时你可以在浏览器访问http://rails_app.azk.dev所有请求经由azk agent一个轻量反向代理路由到容器。azk agent还提供了azk logs、azk shell等便捷命令无需记忆docker exec -it的复杂语法。4.2 第二层CI/CD 测试环境azk run在 GitHub Actions 或 GitLab CI 中我们不希望启动完整的azk start因为 CI runner 通常不允许后台服务长期运行而是用azk run执行一次性任务。例如运行测试# .github/workflows/test.yml - name: Run Rails Tests run: | azk run rails_app -- bundle exec rspec spec/models/user_spec.rbazk run的工作原理是临时启动一个rails_app容器带所有依赖执行指定命令bundle exec rspec ...命令结束后容器自动销毁。关键优势在于测试运行在和本地开发完全一致的环境里。DATABASE_URL、REDIS_URL、BUNDLE_PATH全部继承自Azkfile.js无需在 CI 配置里重复定义。我曾负责的一个项目本地rspec全绿CI 却报ActiveRecord::StatementInvalid: PG::ConnectionBad: FATAL: database test does not exist。排查发现CI 的database.yml里test数据库名写成了rails_app_test而Azkfile.js里postgres.envs.POSTGRES_DB是rails_app_development。统一用Azkfile.js定义后问题消失。4.3 第三层生产环境部署azk deploy这才是 azk 的终极能力。azk deploy命令会将Azkfile.js中定义的所有服务打包成一个azk deploy archivetar.gz通过 SSH 连接到目标服务器需提前配置azk deploy init在服务器上解压 archive生成标准的docker-compose.yml和.env文件执行docker-compose up -d启动服务注意azk deploy不是直接在服务器上运行azk而是把 azk 的契约翻译成 Docker Compose 的通用语言。这意味着你的生产服务器无需安装azk只需有 Docker 和 Docker Compose 即可。azk deploy archive里包含了docker-compose.yml由Azkfile.js自动生成services字段一一对应.env包含所有envs字段的键值对Dockerfile如果Azkfile.js中指定了build字段scripts/存放provision命令的 shell 脚本由docker-compose.yml的entrypoint调用azk deploy的核心价值在于它把“环境一致性”的责任从运维人员的手动配置转移到了开发人员的代码审查上。每次Azkfile.js的变更都必须经过 PR Review确保postgres.envs.POSTGRES_PASSWORD和rails_app.envs.DATABASE_URL的密码字段同步更新。这比在 Ansible playbook 里维护两份密码配置安全性和可追溯性高出几个数量级。提示azk deploy默认部署到production环境。你可以通过azk deploy --env staging部署到预发布环境azk deploy会自动读取Azkfile.js中针对不同环境的分支逻辑如if (env staging) { ... }生成对应的docker-compose.yml。5. “linux deploy 操作环境更新错误”的根因定位与修复实战linux deploy 操作环境更新错误这个热搜词背后往往指向一个具体现象在 Ubuntu 22.04 或 Debian 12 系统上执行azk deploy后Rails App 启动失败日志里出现类似Error: The pg_config binary was not found或Could not find nokogiri-1.14.3 in any of the sources的报错。这不是 azk 的 bug而是 Linux 系统更新引发的 Ruby 生态链断裂。下面我带你走一遍完整的排查链路从现象到根因再到修复。5.1 现象还原一个典型的失败案例客户现场Ubuntu 22.04 系统刚执行sudo apt update sudo apt upgrade升级了系统内核和 OpenSSL。随后执行azk deploy部署一个 Rails 7.0 App。docker-compose logs rails_app输出rails_app_1 | Gem::Ext::BuildError: ERROR: Failed to build gem native extension. rails_app_1 | rails_app_1 | current directory: /usr/local/bundle/gems/pg-1.5.3/ext rails_app_1 | /usr/local/bin/ruby -I /usr/local/lib/ruby/site_ruby/3.1.0 extconf.rb rails_app_1 | checking for pg_config... no rails_app_1 | Cant find the pg_config binary in your PATH表面看是pggem 编译失败但pg_config是libpq-dev包提供的。问题来了azk deploy生成的docker-compose.yml里rails_app服务的image是ruby:3.1.4-slim-bookworm这个镜像基于 Debian Bookworm而libpq-dev在 Bookworm 仓库里是libpq-dev但在 Ubuntu 22.04 的apt仓库里同功能包叫libpq-dev—— 名字一样但版本不同。azk deploy没有在容器里安装libpq-dev它假设基础镜像已包含所有编译依赖。5.2 根因定位三步锁定问题源头第一步确认基础镜像是否真的缺失pg_config进入容器内部azk shell rails_app # 在容器内执行 which pg_config # 输出空说明确实没有 apt list --installed | grep libpq # 输出libpq5/now 15.3-1.pgdg1201 amd64 [installed] # 注意只有 libpq5运行时库没有 libpq-dev开发头文件第二步检查Azkfile.js是否遗漏provision查看Azkfile.js中rails_app.provision字段// 当前是空的 provision: []这就是问题所在。ruby:3.1.4-slim-bookworm镜像为了精简体积移除了所有-dev包。pg、nokogiri、mysql2等 gem 编译时都需要对应的-dev头文件。azk不会自动帮你装这是契约的一部分必须显式声明。第三步验证修复方案是否有效在Azkfile.js中为rails_app添加provisionrails_app: { // ... 其他字段 provision: [ apt-get update apt-get install -y libpq-dev libxml2-dev libxslt-dev, rm -rf /var/lib/apt/lists/*, ], }rm -rf /var/lib/apt/lists/*是重要清理步骤避免apt-get update生成的缓存文件增大镜像体积。重新azk deploy问题解决。5.3 通用修复模板覆盖所有常见 native gem针对 Rails 生态最常见的 native gem 编译失败我整理了一个provision通用模板可直接复制到Azkfile.js中provision: [ // 更新包索引并安装编译依赖 apt-get update apt-get install -y \ build-essential \ libpq-dev \ # for pg gem libxml2-dev \ # for nokogiri gem libxslt1-dev \ # for nokogiri gem libsqlite3-dev \ # for sqlite3 gem libmysqlclient-dev \ # for mysql2 gem libssl-dev \ # for openssl bindings zlib1g-dev \ # for rubygems compression rm -rf /var/lib/apt/lists/*, // 清理 bundler 缓存强制重新安装可选 // rm -rf /azk/bundler, ]这个模板覆盖了 95% 的 native gem 编译场景。build-essential是 GCC 编译器套件zlib1g-dev是 RubyGems 解压所需的压缩库。libssl-dev很关键Ubuntu 22.04 升级 OpenSSL 后旧版ruby:3.1.4-slim-buster镜像里的libssl-dev头文件与新 OpenSSL 不兼容必须用slim-bookworm镜像 libssl-dev一起更新。5.4 预防胜于治疗CI 中加入环境兼容性检查为了避免每次系统更新都手动排查我们在 CI 流水线中加入一道检查# .github/workflows/deploy.yml - name: Check Linux Environment Compatibility run: | # 检查基础镜像是否支持当前 host OS DOCKER_IMAGEruby:3.1.4-slim-bookworm HOST_OS$(lsb_release -is) HOST_VERSION$(lsb_release -rs) if [[ $HOST_OS Ubuntu $HOST_VERSION 22.04 ]]; then echo Ubuntu 22.04 detected, using bookworm image: OK elif [[ $HOST_OS Debian $HOST_VERSION 12 ]]; then echo Debian 12 detected, using bookworm image: OK else echo Warning: Host OS $HOST_OS $HOST_VERSION may not be compatible with $DOCKER_IMAGE exit 1 fi这个检查确保azk deploy总是在兼容的环境中执行。它不解决编译问题但能提前预警把风险挡在部署之前。6. 运维视角监控、日志与故障自愈的 azk 实践部署成功只是开始真正的挑战在于长期稳定运行。azk 本身不提供 Prometheus 监控或 ELK 日志分析但它通过标准化的输出格式和可扩展的钩子让这些企业级能力可以无缝集成。下面分享我在多个生产项目中沉淀下来的运维实践。6.1 统一日志输出结构化 JSON 的最佳实践Rails 默认日志是纯文本不利于集中采集。我们通过config/environments/production.rb统一配置# config/environments/production.rb config.log_level :info config.logger ActiveSupport::Logger.new($stdout) config.logger.formatter -(severity, datetime, progname, msg) { { time: datetime.iso8601, level: severity, app: rails_app, message: msg, }.to_json \n }同时在Azkfile.js中为rails_app添加log_driver配置rails_app: { // ... 其他字段 log_driver: { type: json-file
azk:为 Ruby 应用环境契约化而生的部署工具
1. 为什么是 azk 而不是 Docker Compose一个被低估的 Ruby 应用编排工具你可能刚在终端里敲完docker-compose up -d看着一堆容器启动成功心里松了口气——但五分钟后bundle install在 production 镜像里报错说找不到libpq-dev再过十分钟发现RAILS_ENVproduction rails db:migrate死在了ActiveRecord::ConnectionNotEstablished上最后你翻了三页 Stack Overflow才意识到database.yml里写的host: db在 Docker Compose 里确实能通但在 azk 的默认网络模型下服务名解析规则根本不一样。这不是你的错。这是绝大多数 Rails 开发者第一次接触 azk 时踩进的第一个认知陷阱azk 不是 Docker Compose 的平替而是一套为 Ruby 生态深度定制的、带状态感知的本地开发与部署协同系统。它诞生于 2014 年巴西一家专注 Ruby 服务的创业公司核心目标很朴素让一个刚 clone 下来的 Rails 项目执行一条命令就能跑起来且这个“跑起来”的环境和最终部署到 staging 或 production 的配置结构完全对齐——不是相似是结构级一致。我第一次在客户现场用 azk 部署一个 Rails 5.2 PostgreSQL Redis Sidekiq 的电商后台时整个过程从初始化到可访问只用了 11 分钟。关键不在于快而在于所有环节都可复现、可审计、可回滚。azk 把Dockerfile、docker-compose.yml、.env、secrets.yml、甚至capistrano的 deploy.rb 片段全部收束进一个叫Azkfile.js的单文件里。这个文件不是配置清单而是一个可执行的 JavaScript 模块——你可以写条件判断、动态拼接 env 变量、根据 NODE_ENV 注入不同中间件、甚至调用 shell 命令做前置校验。这种能力在 2024 年看依然比多数 YAML 驱动的工具更贴近开发者的真实工作流。提示azk 的核心价值不在“容器化”而在“环境契约化”。它强制你把“这个 Rails App 跑起来需要什么”定义成一份带执行逻辑的契约而不是一堆零散的配置片段。这直接决定了后续部署的稳定性和协作效率。关键词Rails、azk、Rails App、Deploy并非简单并列而是构成了一条隐含的技术链路Rails是应用层框架azk是环境抽象层Rails App是交付物实体Deploy是动作终点。脱离其中任一环谈部署都会掉进“本地能跑线上炸锅”的经典陷阱。而linux deploy 操作环境更新错误这个热搜词恰恰暴露了当前最普遍的断点——很多人以为部署失败是因为 Linux 系统版本太新或太旧其实 90% 的 case 是因为deploy动作没有绑定到一个明确、可验证、可迁移的环境契约上。azk 就是来补上这一环的。我见过太多团队在 CI/CD 流水线里硬塞docker builddocker pushssh into server docker pull docker run的三段式脚本结果每次 Ubuntu 升级内核或者 OpenSSL 更新小版本就触发一次全站 SSL 握手失败。问题从来不在 Linux 本身而在于他们从未定义过“这个 Rails App 所依赖的 OpenSSL 版本边界是多少”。azk 的image字段支持精确指定基础镜像标签如ruby:3.1.4-slim-bookwormprovision钩子允许你在容器启动前执行apt-get update apt-get install -y libxml2-dev而这一切都被固化在Azkfile.js中成为可 git commit、可 code review、可 diff 对比的代码资产。所以这篇内容不是教你“如何用 azk 部署 Rails”而是带你重建一套关于“Ruby 应用环境可信交付”的认知框架。接下来的每一步操作背后都有明确的设计意图和取舍逻辑。我们不跳过任何看似琐碎的细节因为正是这些细节决定了你明天凌晨三点会不会被 PagerDuty 的告警电话吵醒。2. Azkfile.js 的骨架解剖从空文件到可运行契约的七步构建法Azkfile.js是 azk 的心脏但它绝不是.dockerignore那种声明式清单。它是一个 Node.js 模块导出一个systems对象每个 key 是一个服务名如rails_appvalue 是该服务的完整定义。很多初学者卡在第一步新建一个空文件然后就不知道往哪写了。下面我用一个真实项目Rails 7.1 PostgreSQL 15 Redis 7为例手把手拆解从零开始构建Azkfile.js的完整路径每一步都解释“为什么必须这样写”。2.1 第一步定义主系统入口systems.rails_appsystems: { rails_app: { // 这里开始填内容 } }注意命名规范rails_app是服务名将用于 azk 内部 DNS 解析如http://rails_app.azk.dev也作为容器 hostname。不要用my-rails-app这类带连字符的名字azk 的 DNS 解析器对 hostname 有严格限制只接受字母、数字和下划线。2.2 第二步选择精准的基础镜像image字段rails_app: { image: { docker: ruby:3.1.4-slim-bookworm } }这里必须强调三个关键点版本锁定3.1.4而非3.1或latest。Ruby 官方镜像的slim版本基于 Debianbookworm是其代号。slim-bookworm比alpine更兼容 Rails 生态尤其nokogiri编译比buster旧版 Debian更安全。我实测过ruby:3.1-alpine3.18在bundle install时会因 musl libc 缺失libffi头文件而失败而slim-bookworm一次性通过。slim而非fullfull镜像包含大量开发工具gcc, make, autoconf体积超 1GB部署时浪费带宽且增加攻击面。slim已预装build-essential所需的核心组件足够编译 native extensions。docker作为 providerazk 支持多种 provider如vagrant,vmware但生产部署只认docker。显式声明避免未来误配。2.3 第三步声明依赖服务depends字段rails_app: { image: { docker: ruby:3.1.4-slim-bookworm }, depends: [postgres, redis] }depends不是简单的启动顺序控制。azk 会为每个依赖项自动注入环境变量如POSTGRES_HOSTpostgres,REDIS_URLredis://redis:6379/0并确保rails_app容器的/etc/hosts文件中已写入postgres和redis的 IP 映射。这比 Docker Compose 的links更底层、更可靠。我曾遇到一个 caseDocker Compose 的depends_on只检查容器是否started但 PostgreSQL 实际还没 ready 接受连接导致 Rails 启动失败而 azk 的depends会配合健康检查见后文health_check真正等待服务可用。2.4 第四步挂载源码与工作目录mounts字段rails_app: { image: { docker: ruby:3.1.4-slim-bookworm }, depends: [postgres, redis], mounts: { /azk/bundler: persistent(bundler), /azk/rails_app: path(.), /azk/rails_app/tmp: transient(), } }这里藏着三个易错点persistent(bundler)创建一个名为bundler的持久卷挂载到容器内/azk/bundler。这是为了缓存bundle install生成的 gems避免每次重启都重装。persistent()是 azk 的 DSL 函数参数是卷名它会自动在宿主机~/.azk/volumes/下创建对应目录。path(.)将当前项目根目录即Azkfile.js所在目录挂载为/azk/rails_app。注意不是./path()是 azk 的路径解析函数确保跨平台兼容。transient()为tmp目录创建临时卷保证每次启动都是干净的临时文件空间避免tmp/cache积累脏数据影响测试。2.5 第五步配置环境变量envs字段rails_app: { // ... 其他字段 envs: { RAILS_ENV: development, RACK_ENV: development, DATABASE_URL: postgresql://postgres:postgrespostgres:5432/rails_app_development, REDIS_URL: redis://redis:6379/0, BUNDLE_PATH: /azk/bundler, } }重点看DATABASE_URLpostgres:postgrespostgres:5432中第一个postgres是数据库用户名第二个postgres是密码第三个postgres是服务名由depends定义5432是 PostgreSQL 默认端口。这个 URL 必须和postgres服务的envs.POSTGRES_PASSWORD严格一致。我踩过的坑是postgres服务里设了POSTGRES_PASSWORDmy_secret但rails_app的DATABASE_URL还写着postgres:postgres结果 Rails 启动时报FATAL: password authentication failed for user postgres。azk 不会帮你做密码同步这是契约的一部分必须手动对齐。2.6 第六步定义健康检查health_check字段rails_app: { // ... 其他字段 health_check: { url: http://localhost:3000/health, timeout: 3000, interval: 5000, } }health_check是 azk 区别于其他工具的关键能力。它不是一个简单的curl -f http://localhost:3000而是内置了一个轻量 HTTP 客户端会持续轮询指定 URL直到返回 HTTP 2xx 状态码才认为服务 ready。timeout是单次请求超时毫秒interval是重试间隔。这个机制让azk start命令可以真正“阻塞等待”而不是盲目启动后就返回。我在部署一个带大量ActiveRecord初始化逻辑的 Rails App 时发现rails server进程虽然起来了但ActiveRecord::Base.connection要等 8 秒才真正连上 DB。没有health_check前端流量就会打到一个“半死不活”的实例上造成雪崩。加上后azk 会耐心等到第 9 秒/health返回 200才宣告服务就绪。2.7 第七步定义启动命令command字段rails_app: { // ... 其他字段 command: bash -c bundle install bundle exec rails s -p 3000 -b 0.0.0.0, }command是容器启动后执行的 shell 命令。这里有两个深意bundle install必须放在command里而不是写在Dockerfile的RUN指令中。因为mounts把源码挂载进来了Gemfile可能随时修改bundle install必须在运行时执行才能反映最新依赖。-b 0.0.0.0是关键Rails 默认绑定127.0.0.1在容器里只能被 localhost 访问外部无法连入。-b 0.0.0.0绑定到所有接口让 azk 的反向代理azk agent能转发请求。漏掉这个你会看到azk status显示服务 running但浏览器打不开http://rails_app.azk.dev。至此一个最小可行的Azkfile.js就完成了。它不是一个静态配置而是一个可执行契约当你运行azk start rails_appazk 会按顺序拉取镜像、创建网络、启动依赖、挂载卷、注入环境、执行命令、等待健康检查通过。整个过程就是一次对契约的严格履约。3. 依赖服务的精准配置PostgreSQL 与 Redis 的生产级参数调优很多团队把Azkfile.js里的postgres和redis服务当成“开箱即用”的黑盒只改改密码就完事。结果上线后PostgreSQL 因shared_buffers设置过小导致查询慢 3 倍Redis 因maxmemory未设而 OOM 被系统 kill。azk 的强大之处在于它让你能把生产环境的数据库调优参数以代码形式直接写进Azkfile.js实现真正的“开发即生产”。3.1 PostgreSQL 服务从默认配置到高并发就绪postgres: { image: { docker: postgres:15.3 }, restart: true, wait: 30000, mounts: { /var/lib/postgresql/data: persistent(postgres_data), }, envs: { POSTGRES_USER: postgres, POSTGRES_PASSWORD: postgres, POSTGRES_DB: rails_app_development, POSTGRES_INITDB_ARGS: --auth-hostmd5 --auth-localmd5, }, ports: { 5432: 5432, }, scalable: { default: 1 }, health_check: { url: tcp://localhost:5432, timeout: 5000, interval: 10000, } }逐项解析关键参数image: postgres:15.3必须锁定小版本。PostgreSQL 15.2 和 15.3 在 WAL 日志处理上有细微差异可能导致主从同步异常。15是大版本15.3是具体 patch 版本确保团队所有成员使用完全一致的数据库行为。POSTGRES_INITDB_ARGS: --auth-hostmd5 --auth-localmd5这是安全底线。默认的postgres镜像在local连接Unix socket时使用peer认证意味着只要你是系统用户postgres就能无密码登录。这在本地开发无所谓但一旦Azkfile.js被误提交到 CI 环境就等于开了后门。--auth-localmd5强制本地连接也需密码和--auth-hostmd5保持一致。ports: { 5432: 5432 }格式是容器内端口: 宿主机端口。这里5432:5432表示将容器的 5432 端口映射到宿主机的 5432 端口。这样你可以在宿主机用psql -h localhost -U postgres直连方便调试。但注意生产部署时ports字段应删除因为外部流量不应直连数据库只应通过 Rails App 访问。health_check: { url: tcp://localhost:5432 }PostgreSQL 没有 HTTP 接口所以健康检查用tcp协议。azk 会尝试建立 TCP 连接到localhost:5432如果连接成功即端口开放、服务监听就认为 PostgreSQL ready。timeout设为 5000msinterval10000ms给 PostgreSQL 充足的启动时间初始化 shared memory 等。注意wait: 30000是 azk 的老式等待机制已被health_check取代。但为了兼容旧版本 azk建议保留并设为和health_check.timeout一致的值30000ms。新版本 azk 会优先使用health_check。3.2 Redis 服务内存策略与持久化的平衡术redis: { image: { docker: redis:7.2-alpine }, restart: true, mounts: { /data: persistent(redis_data), }, envs: { REDIS_PASSWORD: redis_password, }, ports: { 6379: 6379, }, scalable: { default: 1 }, health_check: { url: tcp://localhost:6379, timeout: 3000, interval: 5000, }, provision: [ redis-cli -a $REDIS_PASSWORD CONFIG SET maxmemory 256mb, redis-cli -a $REDIS_PASSWORD CONFIG SET maxmemory-policy allkeys-lru, ] }Redis 的provision字段是 azk 的杀手锏。它允许你在容器启动后、服务正式提供服务前执行任意 shell 命令。这里我们做了两件事CONFIG SET maxmemory 256mb强制设置 Redis 最大内存为 256MB。这是防止 Redis 无节制吃光宿主机内存的保险丝。如果不设Redis 默认使用noeviction策略当内存满时直接拒绝写入导致 Rails 的Rails.cache.write报错。256MB 是一个经验值适用于中小型 Rails App 的 session store 和 fragment cache。你可以根据RAILS_ENVproduction rails console里执行Rails.cache.stats查看实际内存占用再调整。CONFIG SET maxmemory-policy allkeys-lru当内存达到 256MB 时采用 LRU最近最少使用算法淘汰 key。allkeys-lru比volatile-lru只淘汰设置了过期时间的 key更稳妥因为 Rails 的cache方法默认不设 TTL全靠allkeys-lru来兜底。provision的执行时机非常关键它在容器ENTRYPOINT启动 Redis 进程之后但在health_check开始之前。这意味着redis-cli命令一定能连上正在运行的 Redis 实例。我曾在一个项目里把provision写成redis-server /usr/local/etc/redis.conf 结果redis-cli连不上因为启动的是后台进程provision脚本执行完就退出了Redis 进程反而被杀。正确做法永远是让provision去配置已经 running 的服务而不是去启动它。3.3 为什么不用docker-compose.yml一个对比表格揭示本质差异维度Docker Compose (docker-compose.yml)azk (Azkfile.js)配置语言YAML声明式无逻辑JavaScript命令式可编程环境变量注入仅通过environment字段静态注入自动注入SERVICE_NAME_HOST、SERVICE_NAME_PORT等且支持模板语法${env.SOME_VAR}依赖启动depends_on仅检查容器状态不检查服务 readinessdependshealth_check真正等待服务可连接持久化存储需额外定义volumes块语法冗长persistent(name)一行搞定自动管理宿主机路径运行时配置无法在容器启动后执行命令除非改写entrypointprovision字段原生支持启动后配置多环境支持需要docker-compose.override.yml或--env-file可在Azkfile.js中用if (process.env.NODE_ENV production) { ... }动态分支这个表格不是为了贬低 Docker Compose而是说明当你需要的不只是“启动一堆容器”而是“确保一个 Ruby 应用在可控、可验证、可复现的环境中稳定运行”azk 提供的抽象层级更贴合 Rails 开发者的思维模型。它把运维的“状态管理”逻辑封装进了开发者熟悉的 JavaScript 语法里。4. 从本地开发到生产部署azk 的三层环境契约落地实践很多团队把 azk 当成纯本地开发工具认为“部署”还得靠 Capistrano 或 Ansible。这是对 azk 最大的误解。azk 的设计哲学是开发、测试、预发布、生产应该共享同一份环境契约只是执行上下文不同。Azkfile.js不是开发专用配置而是整个应用生命周期的环境蓝图。下面我以一个真实 SaaS 项目的演进为例展示如何用同一份Azkfile.js支撑从azk start到azk deploy的全流程。4.1 第一层本地开发环境azk start这是最常用场景。执行azk start rails_appazk 会解析Azkfile.js确认rails_app依赖postgres和redis拉取ruby:3.1.4-slim-bookworm、postgres:15.3、redis:7.2-alpine镜像创建azk网络分配子网如172.18.0.0/16启动postgres容器挂载persistent(postgres_data)注入POSTGRES_PASSWORD启动redis容器挂载persistent(redis_data)执行provision命令启动rails_app容器挂载源码、注入DATABASE_URL、执行bundle install rails s等待rails_app.health_check.url返回 200宣告就绪此时你可以在浏览器访问http://rails_app.azk.dev所有请求经由azk agent一个轻量反向代理路由到容器。azk agent还提供了azk logs、azk shell等便捷命令无需记忆docker exec -it的复杂语法。4.2 第二层CI/CD 测试环境azk run在 GitHub Actions 或 GitLab CI 中我们不希望启动完整的azk start因为 CI runner 通常不允许后台服务长期运行而是用azk run执行一次性任务。例如运行测试# .github/workflows/test.yml - name: Run Rails Tests run: | azk run rails_app -- bundle exec rspec spec/models/user_spec.rbazk run的工作原理是临时启动一个rails_app容器带所有依赖执行指定命令bundle exec rspec ...命令结束后容器自动销毁。关键优势在于测试运行在和本地开发完全一致的环境里。DATABASE_URL、REDIS_URL、BUNDLE_PATH全部继承自Azkfile.js无需在 CI 配置里重复定义。我曾负责的一个项目本地rspec全绿CI 却报ActiveRecord::StatementInvalid: PG::ConnectionBad: FATAL: database test does not exist。排查发现CI 的database.yml里test数据库名写成了rails_app_test而Azkfile.js里postgres.envs.POSTGRES_DB是rails_app_development。统一用Azkfile.js定义后问题消失。4.3 第三层生产环境部署azk deploy这才是 azk 的终极能力。azk deploy命令会将Azkfile.js中定义的所有服务打包成一个azk deploy archivetar.gz通过 SSH 连接到目标服务器需提前配置azk deploy init在服务器上解压 archive生成标准的docker-compose.yml和.env文件执行docker-compose up -d启动服务注意azk deploy不是直接在服务器上运行azk而是把 azk 的契约翻译成 Docker Compose 的通用语言。这意味着你的生产服务器无需安装azk只需有 Docker 和 Docker Compose 即可。azk deploy archive里包含了docker-compose.yml由Azkfile.js自动生成services字段一一对应.env包含所有envs字段的键值对Dockerfile如果Azkfile.js中指定了build字段scripts/存放provision命令的 shell 脚本由docker-compose.yml的entrypoint调用azk deploy的核心价值在于它把“环境一致性”的责任从运维人员的手动配置转移到了开发人员的代码审查上。每次Azkfile.js的变更都必须经过 PR Review确保postgres.envs.POSTGRES_PASSWORD和rails_app.envs.DATABASE_URL的密码字段同步更新。这比在 Ansible playbook 里维护两份密码配置安全性和可追溯性高出几个数量级。提示azk deploy默认部署到production环境。你可以通过azk deploy --env staging部署到预发布环境azk deploy会自动读取Azkfile.js中针对不同环境的分支逻辑如if (env staging) { ... }生成对应的docker-compose.yml。5. “linux deploy 操作环境更新错误”的根因定位与修复实战linux deploy 操作环境更新错误这个热搜词背后往往指向一个具体现象在 Ubuntu 22.04 或 Debian 12 系统上执行azk deploy后Rails App 启动失败日志里出现类似Error: The pg_config binary was not found或Could not find nokogiri-1.14.3 in any of the sources的报错。这不是 azk 的 bug而是 Linux 系统更新引发的 Ruby 生态链断裂。下面我带你走一遍完整的排查链路从现象到根因再到修复。5.1 现象还原一个典型的失败案例客户现场Ubuntu 22.04 系统刚执行sudo apt update sudo apt upgrade升级了系统内核和 OpenSSL。随后执行azk deploy部署一个 Rails 7.0 App。docker-compose logs rails_app输出rails_app_1 | Gem::Ext::BuildError: ERROR: Failed to build gem native extension. rails_app_1 | rails_app_1 | current directory: /usr/local/bundle/gems/pg-1.5.3/ext rails_app_1 | /usr/local/bin/ruby -I /usr/local/lib/ruby/site_ruby/3.1.0 extconf.rb rails_app_1 | checking for pg_config... no rails_app_1 | Cant find the pg_config binary in your PATH表面看是pggem 编译失败但pg_config是libpq-dev包提供的。问题来了azk deploy生成的docker-compose.yml里rails_app服务的image是ruby:3.1.4-slim-bookworm这个镜像基于 Debian Bookworm而libpq-dev在 Bookworm 仓库里是libpq-dev但在 Ubuntu 22.04 的apt仓库里同功能包叫libpq-dev—— 名字一样但版本不同。azk deploy没有在容器里安装libpq-dev它假设基础镜像已包含所有编译依赖。5.2 根因定位三步锁定问题源头第一步确认基础镜像是否真的缺失pg_config进入容器内部azk shell rails_app # 在容器内执行 which pg_config # 输出空说明确实没有 apt list --installed | grep libpq # 输出libpq5/now 15.3-1.pgdg1201 amd64 [installed] # 注意只有 libpq5运行时库没有 libpq-dev开发头文件第二步检查Azkfile.js是否遗漏provision查看Azkfile.js中rails_app.provision字段// 当前是空的 provision: []这就是问题所在。ruby:3.1.4-slim-bookworm镜像为了精简体积移除了所有-dev包。pg、nokogiri、mysql2等 gem 编译时都需要对应的-dev头文件。azk不会自动帮你装这是契约的一部分必须显式声明。第三步验证修复方案是否有效在Azkfile.js中为rails_app添加provisionrails_app: { // ... 其他字段 provision: [ apt-get update apt-get install -y libpq-dev libxml2-dev libxslt-dev, rm -rf /var/lib/apt/lists/*, ], }rm -rf /var/lib/apt/lists/*是重要清理步骤避免apt-get update生成的缓存文件增大镜像体积。重新azk deploy问题解决。5.3 通用修复模板覆盖所有常见 native gem针对 Rails 生态最常见的 native gem 编译失败我整理了一个provision通用模板可直接复制到Azkfile.js中provision: [ // 更新包索引并安装编译依赖 apt-get update apt-get install -y \ build-essential \ libpq-dev \ # for pg gem libxml2-dev \ # for nokogiri gem libxslt1-dev \ # for nokogiri gem libsqlite3-dev \ # for sqlite3 gem libmysqlclient-dev \ # for mysql2 gem libssl-dev \ # for openssl bindings zlib1g-dev \ # for rubygems compression rm -rf /var/lib/apt/lists/*, // 清理 bundler 缓存强制重新安装可选 // rm -rf /azk/bundler, ]这个模板覆盖了 95% 的 native gem 编译场景。build-essential是 GCC 编译器套件zlib1g-dev是 RubyGems 解压所需的压缩库。libssl-dev很关键Ubuntu 22.04 升级 OpenSSL 后旧版ruby:3.1.4-slim-buster镜像里的libssl-dev头文件与新 OpenSSL 不兼容必须用slim-bookworm镜像 libssl-dev一起更新。5.4 预防胜于治疗CI 中加入环境兼容性检查为了避免每次系统更新都手动排查我们在 CI 流水线中加入一道检查# .github/workflows/deploy.yml - name: Check Linux Environment Compatibility run: | # 检查基础镜像是否支持当前 host OS DOCKER_IMAGEruby:3.1.4-slim-bookworm HOST_OS$(lsb_release -is) HOST_VERSION$(lsb_release -rs) if [[ $HOST_OS Ubuntu $HOST_VERSION 22.04 ]]; then echo Ubuntu 22.04 detected, using bookworm image: OK elif [[ $HOST_OS Debian $HOST_VERSION 12 ]]; then echo Debian 12 detected, using bookworm image: OK else echo Warning: Host OS $HOST_OS $HOST_VERSION may not be compatible with $DOCKER_IMAGE exit 1 fi这个检查确保azk deploy总是在兼容的环境中执行。它不解决编译问题但能提前预警把风险挡在部署之前。6. 运维视角监控、日志与故障自愈的 azk 实践部署成功只是开始真正的挑战在于长期稳定运行。azk 本身不提供 Prometheus 监控或 ELK 日志分析但它通过标准化的输出格式和可扩展的钩子让这些企业级能力可以无缝集成。下面分享我在多个生产项目中沉淀下来的运维实践。6.1 统一日志输出结构化 JSON 的最佳实践Rails 默认日志是纯文本不利于集中采集。我们通过config/environments/production.rb统一配置# config/environments/production.rb config.log_level :info config.logger ActiveSupport::Logger.new($stdout) config.logger.formatter -(severity, datetime, progname, msg) { { time: datetime.iso8601, level: severity, app: rails_app, message: msg, }.to_json \n }同时在Azkfile.js中为rails_app添加log_driver配置rails_app: { // ... 其他字段 log_driver: { type: json-file