{"id":51675062,"url":"https://github.com/elixir-vibe/release_kit","last_synced_at":"2026-07-15T04:34:04.730Z","repository":{"id":366300344,"uuid":"1275052239","full_name":"elixir-vibe/release_kit","owner":"elixir-vibe","description":"Build OTP release tarballs with deployment-neutral artifact manifests","archived":false,"fork":false,"pushed_at":"2026-06-20T12:34:01.000Z","size":35,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-21T08:12:54.459Z","etag":null,"topics":["beam","deployment","elixir","mix-release","otp","release"],"latest_commit_sha":null,"homepage":null,"language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/elixir-vibe.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-06-20T07:29:17.000Z","updated_at":"2026-06-20T12:32:07.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/elixir-vibe/release_kit","commit_stats":null,"previous_names":["elixir-vibe/release_kit"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/elixir-vibe/release_kit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elixir-vibe%2Frelease_kit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elixir-vibe%2Frelease_kit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elixir-vibe%2Frelease_kit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elixir-vibe%2Frelease_kit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/elixir-vibe","download_url":"https://codeload.github.com/elixir-vibe/release_kit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elixir-vibe%2Frelease_kit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35490702,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-15T02:00:06.706Z","response_time":131,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["beam","deployment","elixir","mix-release","otp","release"],"created_at":"2026-07-15T04:34:04.052Z","updated_at":"2026-07-15T04:34:04.723Z","avatar_url":"https://github.com/elixir-vibe.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ReleaseKit\n\nReleaseKit builds deployment-neutral artifacts from Mix OTP releases.\n\nIt is for Elixir applications that want a repeatable build product without tying\nthat build product to a deploy tool. ReleaseKit produces ordinary files:\n\n- an OTP release tarball;\n- an ETF manifest describing the tarball, runtime command, runtime environment\n  hints, and optional HTTP health check.\n\nDeployment tools such as HostKit can consume the manifest, but ReleaseKit does\nnot know about systemd, users, Caddy, hosts, or filesystem layouts.\n\n## Why use ReleaseKit?\n\nA deploy pipeline usually needs two separate responsibilities:\n\n1. **Build an application artifact** from source.\n2. **Install and supervise that artifact** on a host.\n\nReleaseKit handles only the first responsibility. It gives downstream deploy\nsystems a small, stable manifest instead of asking every application to invent a\ncustom release tarball format or wrapper Mix task.\n\n## Installation\n\n```elixir\ndef deps do\n  [\n    {:release_kit, \"~\u003e 0.3.0\", only: [:dev, :prod], runtime: false}\n  ]\nend\n```\n\n## Quick start\n\nBuild a production artifact for the default Mix release:\n\n```sh\nMIX_ENV=prod mix release_kit.artifact --out-dir _build/prod/artifacts\n```\n\nFor a web service, record health-check metadata:\n\n```sh\nMIX_ENV=prod mix release_kit.artifact \\\n  --out-dir _build/prod/artifacts \\\n  --port 4000 \\\n  --health-path /\n```\n\nThe output names are stable for deployment tooling and include a checksum sidecar:\n\n```text\n_build/prod/artifacts/my_app-20260620-abcdef0.tar.gz\n_build/prod/artifacts/my_app-20260620-abcdef0.tar.gz.sha256\n_build/prod/artifacts/my_app.etf\n```\n\n## Configuration\n\nYou can put artifact defaults in application config and keep the command short:\n\n```elixir\n# config/config.exs\nconfig :release_kit, :artifact,\n  port: 4000,\n  health_path: \"/\",\n  env_clear: %{\n    \"MY_APP_WEB\" =\u003e \"true\",\n    \"MY_APP_PORT\" =\u003e \"4000\",\n    \"RELEASE_DISTRIBUTION\" =\u003e \"none\"\n  }\n```\n\nThen build with:\n\n```sh\nMIX_ENV=prod mix release_kit.artifact --out-dir _build/prod/artifacts\n```\n\nCLI flags override config values when provided.\n\n### Compile policy\n\nReleaseKit owns the compile phase before assembling the Mix release. Mix task\nflags stay behind configuration so artifact builds can use a stable incremental\npolicy:\n\n```elixir\nconfig :release_kit, :artifact,\n  compile: [\n    # default: true; set false only when an earlier pipeline step already ran\n    # MIX_ENV=prod mix compile for the same checkout and build path\n    enabled: true,\n\n    # default: false; avoids full recompilation when an otherwise reusable\n    # build directory is reached through a different absolute path\n    check_cwd: false,\n\n    # optional diagnostics for investigating slow compiles\n    profile: nil\n  ]\n```\n\nOther compile policy options default to Mix's safe behavior and can be disabled\nwhen a pipeline has already performed those checks: `:optional_deps`,\n`:deps_check`, `:archives_check`, `:elixir_version_check`,\n`:protocol_consolidation`, `:validate_compile_env`, and `:listeners`.\n\n### Package cache\n\nReleaseKit caches package fingerprints by default. If the release directory,\nmanifest-relevant options, build metadata, and asset metadata are unchanged, a\nsubsequent artifact build reuses the existing tarball, checksum sidecar, and\nmanifest instead of compressing the release again.\n\n```elixir\nconfig :release_kit, :artifact,\n  package: [cache: true, compression: :gzip]\n```\n\nSet `cache: false` to force the tarball and manifest to be rewritten on every\nartifact build. When release content is unchanged but the artifact version or\nname changes, ReleaseKit reuses package bytes by copying the previous tarball by\ndefault. Use `reuse: false` to disable this or `reuse: :hardlink` to prefer a\nhardlink with copy fallback.\n\nReleaseKit's package backend stays BEAM-native. For local or internal pipelines\nwhere package size matters less than packaging speed, use an uncompressed\ntarball:\n\n```elixir\nconfig :release_kit, :artifact,\n  package: [compression: :none]\n```\n\nThis writes `.tar` artifacts instead of `.tar.gz`.\n\n### Build telemetry\n\nReleaseKit emits `:telemetry` spans for artifact builds. Each phase emits\n`[:release_kit, :artifact, phase, :start]` and `:stop` or `:exception` events,\nwhere `phase` is one of `:build`, `:compile`, `:before_release`, `:assets`,\n`:release`, `:after_release`, and `:package`. OpenTelemetry users can bridge\nthese telemetry events into spans or metrics in their build environment.\n\nFor a local timing summary, enable:\n\n```elixir\nconfig :release_kit, :artifact,\n  telemetry: [summary: true]\n```\n\nExample output:\n\n```text\nReleaseKit timings:\n  build: 5572ms\n  compile: 84ms\n  assets: 56ms\n  release: 404ms\n  package: 5032ms cache_hit?=false\n```\n\n## Phoenix and Volt assets\n\nPhoenix applications that use Volt can configure a first-class asset preset.\nReleaseKit installs locked npm packages, runs `mix volt.build`, records asset\nmetadata in the manifest, and then packages the OTP release:\n\n```elixir\nconfig :release_kit, :artifact,\n  port: 4000,\n  health_path: \"/\",\n  assets: [\n    volt: [profile: :my_app_web, tailwind: true, root: \"assets\", production: true, frozen: true]\n  ]\n```\n\nFor faster local artifact rebuilds, enable Volt incremental policy. It keeps\nVolt's output directory and skips npm install by default, while still allowing\nexplicit overrides:\n\n```elixir\nconfig :release_kit, :artifact,\n  assets: [\n    volt: [profile: :my_app_web, incremental: true]\n  ]\n```\n\nUse `install: true` or `clean: true` when a build needs those phases even under\nincremental policy.\n\nFor a flat Volt config, omit the profile:\n\n```elixir\nconfig :release_kit, :artifact,\n  assets: [volt: [tailwind: true]]\n```\n\nFor umbrellas or multiple web apps, build multiple profiles:\n\n```elixir\nconfig :release_kit, :artifact,\n  assets: [\n    volt: [\n      profiles: [\n        [profile: :public_web, root: \"apps/public_web/assets\", tailwind: true],\n        [profile: :admin_web, root: \"apps/admin_web/assets\", tailwind: true]\n      ]\n    ]\n  ]\n```\n\n## Lifecycle steps\n\nSome applications need generated files inside the OTP release, such as frontend\nassets or generated config. Configure lifecycle steps and still use the same\nReleaseKit task:\n\n```elixir\nconfig :release_kit, :artifact,\n  steps: [\n    before_compile: [],\n    before_release: [{MyApp.ReleaseStep, mode: :prod}],\n    after_release: [],\n    before_package: [],\n    after_package: []\n  ]\n```\n\nA lifecycle step is any module that implements the `ReleaseKit.Step` behaviour:\n\n```elixir\ndefmodule MyApp.ReleaseStep do\n  @behaviour ReleaseKit.Step\n\n  @impl true\n  def run(opts) do\n    # build generated files before mix release runs\n    :ok\n  end\nend\n```\n\n`ReleaseKit.Step.Volt` is compiled only when both optional dependencies `:volt`\nand `:npm` are available in the consuming project. It installs locked npm\npackages from the configured asset root, removes Volt's output directory, and\nruns `mix volt.build` before the OTP release is assembled. It supports Volt\nnamed profiles, `--tailwind`, source map mode, public directory, asset URL\nprefix, and the production build toggles exposed by Volt's Mix task.\n\nSee `examples/vanilla` for a minimal Phoenix/Volt app that builds with plain:\n\n```sh\nMIX_ENV=prod mix release_kit.artifact --out-dir _build/prod/artifacts\n```\n\n## Manifest shape\n\nThe manifest is an ETF-encoded `%ReleaseKit.Manifest{}` struct:\n\n```elixir\n%ReleaseKit.Manifest{\n  tool: \"release_kit\",\n  format: :beam_release_artifact,\n  format_version: 2,\n  app: \"my_app\",\n  release: \"my_app\",\n  version: \"20260620-abcdef0\",\n  mix_env: \"prod\",\n  tarball: \"/absolute/path/to/my_app-20260620-abcdef0.tar.gz\",\n  artifact: %ReleaseKit.ArtifactInfo{\n    path: \"/absolute/path/to/my_app-20260620-abcdef0.tar.gz\",\n    size: 1_234_567,\n    compression: :gzip,\n    checksum: %{\n      algorithm: :sha256,\n      value: \"...\",\n      sidecar: \"/absolute/path/to/my_app-20260620-abcdef0.tar.gz.sha256\"\n    }\n  },\n  target: %ReleaseKit.Target{os: \"linux\", arch: \"x86_64\", libc: \"glibc\"},\n  build: %ReleaseKit.BuildInfo{\n    release_kit_version: \"0.3.0\",\n    elixir_version: \"1.20.0\",\n    otp_version: \"28\",\n    git_revision: \"abcdef0\",\n    git_dirty?: false\n  },\n  assets: [\n    %ReleaseKit.AssetInfo{\n      tool: :volt,\n      profile: :my_app_web,\n      outdir: \"priv/static/assets\",\n      manifest: \"priv/static/assets/manifest.json\",\n      asset_url_prefix: \"/assets\",\n      tailwind?: true\n    }\n  ],\n  runtime: %{command: [\"bin/my_app\", \"start\"]},\n  env: %{\n    clear: %{\"MY_APP_WEB\" =\u003e \"true\"},\n    secret: []\n  },\n  health_check: %{\n    path: \"/\",\n    port: 4000,\n    url: \"http://127.0.0.1:4000/\"\n  }\n}\n```\n\n## Task options\n\n```text\n--out-dir PATH       Directory for the tarball and manifest\n--release NAME       Mix release name; defaults to the app name\n--version VERSION    Artifact version; defaults to YYYYMMDD-gitsha\n--port PORT          HTTP port recorded in health-check metadata\n--health-path PATH   HTTP path recorded in health-check metadata\n--skip-release       Package an existing _build/.../rel release directory\n--target-os OS       Override the manifest target OS\n--target-arch ARCH   Override the manifest target architecture\n--target-libc LIBC   Override the manifest target libc\n--target-suffix      Include the target in artifact and manifest filenames\n```\n\n## Deployment tool contract\n\nReleaseKit intentionally stops after producing files. A deployment system should:\n\n1. read the ETF manifest with `:erlang.binary_to_term/1`;\n2. verify `artifact.checksum` against the tarball or `.sha256` sidecar;\n3. unpack the tarball into the deployment-specific release directory;\n4. materialize `env.clear` and resolve secret names from `env.secret`;\n5. start `runtime.command` from the unpacked release root;\n6. supervise the release with the host's process manager;\n7. poll `health_check.url` when present before promoting traffic.\n\nManifest fields are additive. Consumers should ignore unknown keys whose\n`format_version` they support.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felixir-vibe%2Frelease_kit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Felixir-vibe%2Frelease_kit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felixir-vibe%2Frelease_kit/lists"}