资讯动态

Phoenix 应用部署到 Heroku 完整实战指南:Buildpack 与 Container 双方案详解

发布时间:2026/9/20 5:21:15 来源:尧图企业网站定制
Phoenix 应用部署到 Heroku 完整实战指南Buildpack 与 Container 双方案详解【免费下载链接】phoenixPeace of mind from prototype to production项目地址: https://gitcode.com/gh_mirrors/ph/phoenix本指南基于 Phoenix 官方部署文档 guides/deployment/heroku.md 编写目标是把一个可运行的 Phoenix 应用完整部署到 Heroku 平台。你将掌握通过 Elixir Buildpack 与 Container 容器栈两种方式部署的完整流程、elixir_buildpack.config/Procfile/heroku.yml等关键配置文件、生产环境 SSL 与 WebSocket 超时的正确配置以及环境变量、数据库迁移与常见故障排查的实战技巧。前置条件一个可工作的 Phoenix 应用本指南唯一需要的硬性条件是一个本地可运行的 Phoenix 应用。如果你还没有应用可以部署请先跟随 Up and Running 入门指南 创建一个基础应用再继续。在动手部署前也建议先通读 部署概述Deployment 简介它梳理了生产部署的三步主线应用密钥secrets处理 → 静态资源编译 → 生产环境启动服务器。Heroku 指南正是这条主线在 PaaS 平台上的具体落地。认识平台限制Heroku 与 Elixir/Phoenix 的边界Heroku 是一个优秀的平台Elixir 在其上运行良好。但如果你打算使用 Elixir 和 Phoenix 的进阶特性需要注意以下限制连接数受限Heroku 限制了同时连接数与每个连接的持续时间。Elixir 常被用于实时应用这类应用需要大量并发长连接——Phoenix 官方博客曾展示过单服务器支撑超 200 万 WebSocket 连接的场景而 Heroku 的路由层会限制这种能力。无法分布式集群Heroku 的防火墙将 dyno 相互隔离这意味着分布式 Phoenix Channels、分布式任务等能力无法直接依赖 Erlang 内建分发需要借助 Redis 之类的中间件。内存态数据每 24 小时丢失Agent、GenServer、ETS 中保存的内存状态会因 Heroku 每 24 小时强制重启 dyno 而全部丢失无论节点是否健康。内建 observer 不可用Heroku 虽然允许连接进 dyno但你无法用 observer 观察 dyno 内部状态。什么时候 Heroku 够用如果你刚起步或并不依赖上述特性Heroku 完全足够。例如把运行在 Heroku 上的现有应用迁移到 Phoenix且功能集相近时Elixir 的表现会与现有技术栈相当甚至更好。如果需要一个没有这些限制的 PaaS官方文档侧边栏列有其他替代方案如果倾向自管云主机EC2、Google Cloud 等建议改用mix release部署详见 releases 指南其中包含可直接使用的示例 Dockerfile仓库模板见 priv/templates/phx.gen.release/Dockerfile.eex。部署步骤总览整个流程可以拆解为以下几步便于跟踪进度初始化 Git 仓库注册 Heroku 账号安装 Heroku ToolbeltCLI创建并配置 Heroku 应用让项目适配 Heroku正式部署常用 Heroku 命令初始化 Git 仓库Heroku 通过 Git 推送代码完成部署因此在推送之前需要先在项目目录初始化本地 Git 仓库并提交文件$ git init $ git add . $ git commit -m Initial commit注册 Heroku 账号前往 Heroku 官网注册页填写表单即可完成注册。Free 计划会提供 1 个 web dyno 和 1 个 worker dyno并附带免费的 PostgreSQL 与 Redis 实例——这些资源定位是测试与开发存在各种限制要运行生产应用请升级到付费套餐。安装 Heroku Toolbelt注册完成后下载对应系统的 Heroku Toolbelt。其中包含的Heroku CLI非常有用可以完成创建 Heroku 应用、列出某应用正在运行的 dyno、实时查看日志tail logs、以及运行一次性命令例如执行 mix 任务。方式一通过 Buildpack 创建并配置 Heroku 应用在 Heroku 上部署 Phoenix 应用有两条路线使用 Heroku buildpacks或使用其 container 栈。两者的核心区别在于如何告诉 Heroku 处理构建过程Buildpack 路线需要在 Heroku 上配置 Phoenix/Elixir 专属 buildpack由平台按既定流程编译Container 路线通过Dockerfile和heroku.yml完全自定义容器镜像对应用组装拥有更多控制权通常建议配合 release 使用后面详述。本节先深入 buildpack 路线。创建应用两个必备 BuildpackBuildpack 是一种打包框架/运行时支持的便捷机制。Phoenix 需要两个 buildpack 才能在 Heroku 上运行第一个提供基础 Elixir 支持第二个补充 Phoenix 专属命令。安装好 Toolbelt 后用最新版 Elixir buildpack 创建应用$ heroku create --buildpack hashnuke/elixir Creating app... done, ⬢ mysterious-meadow-6277 Setting buildpack to hashnuke/elixir... done https://mysterious-meadow-6277.herokuapp.com/ | https://git.heroku.com/mysterious-meadow-6277.git几个要点说明首次使用 Heroku 命令时可能会提示登录输入注册时的邮箱和密码即可输出中 Creating 后面的随机字符串mysterious-meadow-6277就是应用名每次创建都会不同输出里的 URL 就是应用地址现在在浏览器打开会看到 Heroku 默认欢迎页如果执行heroku create之前没有初始化 Git 仓库此时 Heroku 远程仓库不会自动配置好需要手动执行heroku git:remote -a [your-app-name]补上。固定 Elixir / Erlang 版本elixir_buildpack.configBuildpack 内置了一套预定义的 Elixir 和 Erlang 版本。为避免部署时出现意外最佳实践是在elixir_buildpack.config中显式声明与开发环境、CI 环境一致的版本。在项目根目录创建该文件# Elixir version elixir_version1.18.4 # Erlang version # https://github.com/HashNuke/heroku-buildpack-elixir-otp-builds/blob/master/otp-versions erlang_version27.0 # Invoke assets.deploy defined in your mix.exs to deploy assets with esbuild # Note we nuke the esbuild executable from the image hook_post_compileeval mix assets.deploy rm -f _build/esbuild*这里hook_post_compile调用的mix assets.deploy是 Phoenix 项目mix.exs中定义的 alias——它负责编译资源并生成带摘要的静态清单文件供生产环境快速服务资源参见 部署概述 中“编译应用资源”一节本仓库的 alias 定义见 mix.exs。声明启动方式Procfile接着在项目根目录创建Procfile告诉 buildpack 如何启动 Web 服务器web: mix phx.server可选Node / npm 与 Phoenix Static buildpack默认情况下Phoenix 使用esbuild替你管理全部前端资源。但如果你用的是node和npm就需要额外安装 Phoenix Static buildpack 来处理它们$ heroku buildpacks:add https://github.com/gigalixir/gigalixir-buildpack-phoenix-static.git Buildpack added. Next release on mysterious-meadow-6277 will use: 1. https://github.com/HashNuke/heroku-buildpack-elixir.git 2. https://github.com/gigalixir/gigalixir-heroku-buildpack-phoenix-static.git使用该 buildpack 时应把资源打包全部委托给npm因此需要从elixir_buildpack.config中移除hook_post_compile把它挪到assets/package.json的 deploy 脚本里{ ... scripts: { deploy: cd .. mix assets.deploy rm -f _build/esbuild* } ... }同样地Phoenix Static buildpack 内置了预定义 Node.js 版本为消除部署差异请在项目根目录创建phoenix_static_buildpack.config显式声明版本# Node.js version node_version10.20.1完整配置项请参考该 buildpack 的官方文档你也可以编写自定义构建脚本这里使用其默认脚本即可。最后注意由于使用了多个 buildpack可能出现顺序错乱的问题Elixir buildpack 必须先于 Phoenix Static buildpack 运行。请务必确保Phoenix Static buildpack 排在最后。让项目适配 HerokuSSL、Host 与 WebSocket 超时每个新 Phoenix 项目都会自带config/runtime.exs它在**启动时boot time**从环境变量加载配置和密钥——这与 Heroku 的最佳实践12-factor app天然契合。参考仓库模板 installer/templates/phx_single/config/runtime.exs.eex 可以看到SECRET_KEY_BASE缺失时应用会直接raise并提示用mix phx.gen.secret生成PHX_HOST默认回落到example.comHTTP 端口从PORT环境变量读取。因此剩余工作主要是配置 URL 与 SSL。第一步强制 HTTPS编译期配置告诉 Phoenix 只使用 HTTPS 版本的网站。找到config/prod.exs中的 endpoint 配置config :scaffold, ScaffoldWeb.Endpoint, url: [port: 443, scheme: https],……然后加上force_sslconfig :scaffold, ScaffoldWeb.Endpoint, url: [port: 443, scheme: https], force_ssl: [rewrite_on: [:x_forwarded_proto]],关键原因force_ssl属于编译期compile time配置在runtime.exs中设置不会生效所以必须写在这里。新项目模板 installer/templates/phx_single/config/prod.exs.eex 默认就带上了force_ssl: [rewrite_on: [:x_forwarded_proto], exclude: [...]]的示例其中rewrite_on: [:x_forwarded_proto]正是为了让 Heroku 这类反向代理后面的应用能识别 HTTPS 请求头。第二步配置 Host运行时配置然后在config/runtime.exs中加上hostconfig :scaffold, ScaffoldWeb.Endpoint, url: [host: host, port: 443, scheme: https]第三步数据库连接启用 SSL取消仓库repository配置中# ssl: true,一行的注释最终如下config :hello, Hello.Repo, ssl: true, url: database_url, pool_size: String.to_integer(System.get_env(POOL_SIZE) || 10)第四步降低 WebSocket 超时如果你计划使用 WebSocket需要降低lib/hello_web/endpoint.ex中 WebSocket 传输的超时时间如果不用 WebSocket保持默认即可。参考仓库端点模板 installer/templates/phx_web/endpoint.ex.eexdefmodule HelloWeb.Endpoint do use Phoenix.Endpoint, otp_app: :hello socket /socket, HelloWeb.UserSocket, websocket: [timeout: 45_000] ... end同时在 Heroku 上设置 host$ heroku config:set PHX_HOSTmysterious-meadow-6277.herokuapp.com为什么是 45 秒从 Phoenix 源码lib/phoenix/endpoint.ex的socket/3文档可以看到WebSocket 的:timeout表示“连接最后一次收到数据后保持打开的超时时间”默认是60_000ms。而 Heroku 的 HTTP 超时窗口是55 秒。把 Phoenix 侧的超时设为 45 秒可以确保任何空闲连接都会在到达 Heroku 55 秒超时窗口之前被 Phoenix 主动关闭避免连接被平台意外切断。在 Heroku 中创建环境变量DATABASE_URL 与 POOL_SIZEDATABASE_URL配置变量会在添加 Heroku Postgres add-on 时自动创建。先用 Toolbelt 创建数据库$ heroku addons:create heroku-postgresql:mini然后设置POOL_SIZE$ heroku config:set POOL_SIZE18这个值应略低于可用连接数留出几条给迁移和 mix 任务使用。mini 数据库允许 20 条连接所以这里设为 18。如果多个 dyno 共享同一数据库需要按 dyno 数量均分相应调低POOL_SIZE。之后在 Heroku 上运行 mix 任务时项目已推送之后也要限制其连接池$ heroku run POOL_SIZE2 mix hello.task这样 Ecto 就不会试图打开超过可用上限的连接。SECRET_KEY_BASE还需要基于随机字符串创建SECRET_KEY_BASE。先用mix phx.gen.secret生成新密钥$ mix phx.gen.secret xvafzY4y01jYuzLm3ecJqo008dVnU3CN4fMamNd1Zue4pXvfvUjbiXT8akaIF53你的随机字符串一定不同切勿使用示例值。从实现上看lib/mix/tasks/phx.gen.secret.exmix phx.gen.secret默认生成64 字符的密钥也接受自定义长度参数mix phx.gen.secret [length]最小 32内部通过:crypto.strong_rand_bytes/1取强随机字节再 Base64 编码得到。然后把它设置到 Heroku$ heroku config:set SECRET_KEY_BASExvafzY4y01jYuzLm3ecJqo008dVnU3CN4fMamNd1Zue4pXvfvUjbiXT8akaIF53 Setting config vars and restarting mysterious-meadow-6277... done, v3 SECRET_KEY_BASE: xvafzY4y01jYuzLm3ecJqo008dVnU3CN4fMamNd1Zue4pXvfvUjbiXT8akaIF53该值由config/runtime.exs在启动时读取用于签名/加密 Cookie 与其他机密数据开发/测试环境使用的是模板内置的默认值生产环境必须通过环境变量提供缺失即报错且不应提交到版本控制系统。部署时刻提交所有改动并推送到 Heroku$ git add elixir_buildpack.config $ git commit -a -m Use production config from Heroku ENV variables and decrease socket timeout$ git push heroku main Counting objects: 55, done. Delta compression using up to 8 threads. Compressing objects: 100% (49/49), done. Writing objects: 100% (55/55), 48.48 KiB | 0 bytes/s, done. Total 55 (delta 1), reused 0 (delta 0) remote: Compressing source files... done. remote: Building source: remote: remote: ----- Multipack app detected remote: ----- Fetching custom git buildpack... done remote: ----- elixir app detected remote: ----- Checking Erlang and Elixir versions remote: WARNING: elixir_buildpack.config wasnt found in the app remote: Using default config from Elixir buildpack remote: Will use the following versions: remote: * Stack cedar-14 remote: * Erlang 17.5 remote: * Elixir 1.0.4 remote: Will export the following config vars: remote: * Config vars DATABASE_URL remote: * MIX_ENVprod remote: ----- Stack changed, will rebuild remote: ----- Fetching Erlang 17.5 remote: ----- Installing Erlang 17.5 (changed) remote: remote: ----- Fetching Elixir v1.0.4 remote: ----- Installing Elixir v1.0.4 (changed) remote: ----- Installing Hex remote: 2015-07-07 00:04:00 URL:https://s3.amazonaws.com/s3.hex.pm/installs/1.0.0/hex.ez [262010/262010] - /app/.mix/archives/hex.ez [1] remote: * creating /app/.mix/archives/hex.ez remote: ----- Installing rebar remote: * creating /app/.mix/rebar remote: ----- Fetching app dependencies with mix remote: Running dependency resolution remote: Dependency resolution completed successfully remote: [...] remote: ----- Compiling remote: [...] remote: Generated phoenix_heroku app remote: [...] remote: Consolidated protocols written to _build/prod/consolidated remote: ----- Creating .profile.d with env vars remote: ----- Fetching custom git buildpack... done remote: ----- Phoenix app detected remote: remote: ----- Loading configuration and environment remote: Loading config... remote: [...] remote: Will export the following config vars: remote: * Config vars DATABASE_URL remote: * MIX_ENVprod remote: remote: ----- Compressing... done, 82.1MB remote: ----- Launching... done, v5 remote: https://mysterious-meadow-6277.herokuapp.com/ deployed to Heroku remote: remote: Verifying deploy... done. To https://git.heroku.com/mysterious-meadow-6277.git * [new branch] master - master上面的输出是早期版本的示例日志注意其中WARNING: elixir_buildpack.config wasnt found——当时还没提交该文件所以回落到默认版本今天执行时流程一致但版本号、输出细节会不同。上面的日志也印证了部署流水线检测到 elixir 应用 → 检查/安装 Erlang 与 Elixir → 安装 Hex/rebar → 拉取依赖并编译 → 导出MIX_ENVprod等配置 → 压缩并启动。在终端执行heroku open即可用浏览器打开 Phoenix 欢迎页。如果应用使用 Ecto 访问数据库首次部署后还需要运行迁移$ heroku run POOL_SIZE2 mix ecto.migrate大功告成方式二通过 Container 栈部署创建 Heroku 应用把应用的栈设置为container即可用Dockerfile定义应用组装方式$ heroku create Creating app... done, ⬢ mysterious-meadow-6277 $ heroku stack:set container在项目根目录新增heroku.yml文件可在其中定义应用使用的 addons、镜像构建方式以及传递给镜像的配置。示例setup: addons: - plan: heroku-postgresql as: DATABASE build: docker: web: Dockerfile config: MIX_ENV: prod SECRET_KEY_BASE: $SECRET_KEY_BASE DATABASE_URL: $DATABASE_URL使用 release 并编写 Dockerfile接下来需要在项目根目录定义包含应用的Dockerfile。强烈建议配合 release 使用——release 只打包实际用到的 Erlang/Elixir 部分能显著缩小镜像体积。请先阅读 releases 指南其末尾附有可直接使用的示例 Dockerfile该模板在仓库中的位置是 priv/templates/phx.gen.release/Dockerfile.eex采用多阶段构建builder 阶段安装 Hex/rebar、拉取 prod 依赖、编译代码与资源并mix releasefinal 阶段仅拷贝编译产物以nobody用户运行/app/bin/server。镜像定义就绪后推送应用到 Heroku平台会自动开始构建镜像并部署。常用 Heroku 命令查看应用日志加--tail可实时跟踪$ heroku logs # use --tail if you want to tail them启动一个附加到终端的 IEx 会话在应用环境中做实验$ heroku run POOL_SIZE2 iex -S mix事实上heroku run可以运行任何命令比如前面用到的 Ecto 迁移任务$ heroku run POOL_SIZE2 mix ecto.migrate连接进你的 dyno远程 IEx 调试Heroku 允许用 IEx shell 连接进 dyno从而执行数据库查询等 Elixir 代码。步骤如下修改 Procfile 中的web进程使其运行一个命名节点web: elixir --sname server -S mix phx.server重新部署到 Heroku用heroku ps:exec连接进 dyno同一仓库下若有多个应用需用--app APP_NAME或--remote REMOTE_NAME指定启动远程 IEx 会话iex --sname console --remsh server。之后你就拥有一个通向 dyno 内部 Erlang VM 的 IEx 会话了。注意此方式无法使用 observer 查看状态见前文“平台限制”。故障排查编译错误Compilation Error偶尔会出现“本地编译成功、Heroku 上编译失败”的情况错误形如remote: Compilation error on file lib/postgrex/connection.ex remote: could not compile dependency :postgrex, mix compile failed. You can recompile this dependency with mix deps.compile postgrex, update it with mix deps.update postgrex or clean it with mix deps.clean postgrex remote: ** (CompileError) lib/postgrex/connection.ex:207: Postgrex.Connection.__struct__/0 is undefined, cannot expand struct Postgrex.Connection remote: (elixir) src/elixir_map.erl:58: :elixir_map.translate_struct/4 remote: (stdlib) lists.erl:1353: :lists.mapfoldl/3 remote: (stdlib) lists.erl:1354: :lists.mapfoldl/3 remote: remote: ! Push rejected, failed to compile elixir app remote: remote: Verifying deploy... remote: remote: ! Push rejected to mysterious-meadow-6277. To https://git.heroku.com/mysterious-meadow-6277.git原因通常是过期的依赖没有正确重新编译。可以在应用根目录的elixir_buildpack.config中加入一行强制 Heroku 每次部署都重编所有依赖always_rebuildtrue提交该文件后重新推送即可解决。连接超时错误Connection Timeout Error如果执行heroku run时频繁出现连接超时可能是你的网络运营商屏蔽了 5000 端口heroku run POOL_SIZE2 mix myapp.task Running POOL_SIZE2 mix myapp.task on mysterious-meadow-6277... ! ETIMEDOUT: connect ETIMEDOUT 50.19.103.36:5000解决办法是给 run 命令加上detached选项后台运行heroku run:detached POOL_SIZE2 mix ecto.migrate Running POOL_SIZE2 mix ecto.migrate on mysterious-meadow-6277... done, run.8089 (Free)补充多实例下的 Long-Polling 与状态问题heroku.md通篇假设单一 web dyno 部署。若生产环境运行多台机器这也是官方推荐的做法部署时还需要留意 Long-Polling 传输的问题Phoenix 的 Socket 同时支持 WebSocket 与 Long-Polling 两种传输installer/templates/phx_web/endpoint.ex.eex 中同时声明了websocket:与longpoll:配置客户端会在 WebSocket 连接失败时按配置的longPollFallbackMs自动回退到 Long-Polling前端实现见 assets/js/phoenix/socket.js。由于 Long-Polling 的每个请求都可能落到不同机器要让长连接状态得以保留部署方案必须满足其一启用 Erlang VM 集群能力默认Phoenix.PubSub可跨节点广播、更换Phoenix.PubSub适配器如 Redis、或由部署平台实现粘性会话sticky sessions。这一点在 部署概述 的“Clustering and Long-Polling Transports”一节有更完整的论证——而正如前文“平台限制”所说Heroku 的 dyno 默认互相隔离既不支持原生集群也未必提供粘性会话这正是需要考虑替代 PaaS 或自管主机的原因。【免费下载链接】phoenixPeace of mind from prototype to production项目地址: https://gitcode.com/gh_mirrors/ph/phoenix创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价