Deploying from a pipeline

A health check

A deploy that returns zero has moved files, not necessarily started a service. The last step of a deploy asks the new version a question only it can answer: the health endpoint.

require "net/http" uri = URI(ARGV[0] || "http://localhost:8000/health") response = Net::HTTP.get_response(uri) puts "#{response.code} #{response.body}" exit(response.code == "200" ? 0 : 1)

The Ruby script exits zero only on a 200. Start a small server with a /health route and run the check against it:

require "webrick" server = WEBrick::HTTPServer.new(Port: 8000) server.mount_proc("/health") { |_req, res| res.body = "ok" } server.start

ruby health_check.rb

200 ok

A wrong path or a server that is not up gives a nonzero exit status, and the pipeline marks the deploy failed. That is the whole idea: the check runs against the thing that was just deployed, not against a file that was copied.

The deploy job

A deploy job goes after the tests and the build, and it waits for a person when the environment is production. In GitHub Actions the gate is an environment with required reviewers:

deploy: needs: test runs-on: ubuntu-latest environment: production steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: ruby-version: "3.4" bundler-cache: true - run: bundle exec rake ci - run: bundle exec ruby health_check.rb https://example.com/health

GitLab has the same idea with when: manual on the deploy job and a protected environment, and the health check is the last line of the script. The address comes from a variable, so the same job deploys to staging and production.

Most Ruby deploys copy files over SSH with scp. Those commands — ssh, scp, a container registry login — are the part the pipeline should keep small: one command whose arguments come from variables, so the job shows the address and never the key.

What to watch for

A health check that always answers 200 ok from a static file proves nothing. Make it touch the dependency that matters: the database connection, the cache, the background queue. If the service can be up and useless, the check must ask about the useless part.

Keep a way back. A deploy job that cannot roll back turns a bad release into a long outage; the previous image tag or the previous package is enough, as long as it is named in the job.

Next: Ruby tools a pipeline runs

Article author: Arthur Isaev