Deployment Tracking

Record deployments from CI/CD or a shell script so releases appear as markers on every chart and can be lined up against regressions and exceptions.

Overview

Rails Pulse can record when you deploy. Each deployment is a revision (usually a git SHA), a start time, an optional finish time, and optional metadata. Deployments:

  • Appear as vertical markers on the dashboard, route, query, and job charts, so a regression can be lined up against the release that caused it
  • Are listed on the Deployments page with their status, duration, and metadata
  • Stamp each captured exception with the revision that was live when it fired

There are two ways to record one: an HTTP endpoint for CI/CD pipelines, and rake tasks for shell-based deploys that have database access but no token or network route to the dashboard.

HTTP API

The endpoint lives under the engine mount, so with the default mount it is POST /rails_pulse/deployments. It accepts one credential, config.deployment_token, sent in an X-Rails-Pulse-Token header, and fails closed: with no token configured, every request is rejected.

# config/initializers/rails_pulse.rb
RailsPulse.configure do |config|
  config.deployment_token = ENV["RAILS_PULSE_DEPLOYMENT_TOKEN"]
  # or: Rails.application.credentials.dig(:rails_pulse, :deployment_token)
end

Two other credentials are deliberately refused here:

  • A dashboard login. CSRF protection is off for this endpoint so CI can post to it, so accepting a browser session would let any page a signed-in admin visited record a release. If you have no token, use the rake tasks, which need none.
  • config.api_token. That token is for the read-only JSON API, and is often handed to a coding agent. Keeping the two separate means an agent can read your data but cannot record a release. Set both to the same value if you would rather run one credential.

Renamed in 0.5: deployment_api_token is now deployment_token. The old name still works.

Record a deployment when a release starts:

curl -X POST https://yourapp.com/rails_pulse/deployments \
  -H "X-Rails-Pulse-Token: $RAILS_PULSE_DEPLOYMENT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"deployment": {"revision": "abc1234", "metadata": {"environment": "production"}}}'

Mark it finished when the release completes:

curl -X PUT https://yourapp.com/rails_pulse/deployments/finish \
  -H "X-Rails-Pulse-Token: $RAILS_PULSE_DEPLOYMENT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"deployment": {"revision": "abc1234"}}'

Both accept an optional started_at / finished_at timestamp. started_at may be at most one hour in the future.

GitHub Actions example

- name: Record deployment in Rails Pulse
  run: |
    curl -fsS -X POST "$APP_URL/rails_pulse/deployments" \
      -H "X-Rails-Pulse-Token: ${{ secrets.RAILS_PULSE_DEPLOYMENT_TOKEN }}" \
      -H "Content-Type: application/json" \
      -d "{\"deployment\": {\"revision\": \"${{ github.sha }}\", \"metadata\": {\"actor\": \"${{ github.actor }}\"}}}"

Rake tasks

For Kamal hooks, Capistrano tasks, or any deploy script that can run rails against the production database:

rake rails_pulse:record_deployment[abc1234]
# ... deploy ...
rake rails_pulse:finish_deployment[abc1234]

Rake splits task arguments on commas, so metadata is passed as a JSON object in an environment variable instead:

RAILS_PULSE_DEPLOYMENT_METADATA='{"environment":"production","actor":"ci"}' \
  rake rails_pulse:record_deployment[abc1234]

finish_deployment sets finished_at on the latest deployment recorded for that revision and prints the duration.

Did the release make things worse?

The JSON API, rails-pulse deployments list and the rails_pulse_deployments MCP tool report a comparison on every deployment: the hour before the release against the hour after it, read from the hourly summaries. Response times and the error rate are each marked degraded when they are materially worse afterwards (by the ratio and absolute floor in config.regression_thresholds), clean otherwise, or insufficient_data when either hour had fewer than 10 requests.

The comparison is computed when asked and never stored. It reads hourly summaries, which are kept for config.hourly_summary_retention (default 7 days), so a deploy older than that reports insufficient_data.

Limits and retention

FieldLimit
revision255 characters
metadata4 KB serialized
started_atAt most one hour in the future

Deployments are never pruned by age. They are capped by count instead, so old markers stay on long-range charts until the cap is reached:

config.max_table_records = {
  rails_pulse_deployments: 1_000  # default; oldest pruned first
}

Standalone and separate dashboards

The API endpoint is served by whichever process mounts the dashboard. If your main app runs with mount_dashboard = false, point CI at the dashboard host instead, or use the rake tasks from the main app, which only need database access. See Deployment Modes.

Next Steps