Starting hanami in production environment

Hey hey

I am having some problems with the hanami server command. Namely I cannot start the server in production environment.
I’ve tried:

bundle exec hanami server --host 0.0.0.0 -e production

with HANAMI_ENV set to production, still I was getting my server started in development mode.

I tried with dockerfile on local, on remote server, I tried with just the command, and still nothing.
When I enter the container and check HANAMI_ENV it returns production. Same in hanami console, the env is prod, but the server starts in dev.

Going into documentation:

If HANAMI_ENV is not set, Hanami will fallback to checking RACK_ENV. If neither variable is set, the environment defaults to :development.

From: V2.2: Environments | Hanami Guides

My question: is this a bug? Or is the documentation wrong?

In my opinion if I set the -e production argument when starting the server, that should overwrite all, otherwise I see no point in having this argument if it is overwritten by RACK_ENV anyway, which also seems to overwrite HANAMI_ENV.

Thanks for raising this issue! It sounds like a bug, and we should fix it.

Do you have the hanami-reloader gem bundled when you’re trying to run these commands? I wonder if this is an issue of the env vars getting lost when it shells out to running bundle exec guard.

Either way, you really don’t want the reloader gem bundled when you’re trying to run a prod app. If it’s in the bundle, the first thing I’d look at it making sure it’s not there.

One way to test this theory would be to run hanami server with the --no-code-reloading flag. That bypasses the reloader.

Actually I digged around the CLI repo and immedietly found a comment that casts some doubt:

module Hanami
  module CLI
    module Commands
      module App
        # Launch Hanami web server.
        #
        # It's intended to be used only on development. For production, you
        # should use the rack handler command directly (i.e. `bundle exec puma
        # -C config/puma.rb`).
        #
        # The server is just a thin wrapper on top of Rack::Server. The options that it
        # accepts fall into two different categories:
        #
        # - When not explicitly set, port and host are not passed to the rack
        # server instance. This way, they can be configured through the
        # configured rack handler (e.g., the puma configuration file).
        #
        # - All others are always given by the Hanami command.
        #
        # Run `bundle exec hanami server -h` to see all the supported options.

So it seems that the current code is not intended to be run in production but I am not sure what is the reasoning behind it. Also if we are to stick with this (so using the hanami server command only in dev) then we would need to adjust the documentation, cause the current one suggest that hanami server is suited for both dev and prod. Not sure how to proceed in that case, should I treat this as a bug, adjust the comment and try to make the server be able to run in production mode or should we adjust the documentation and keep relying on puma directly?

The command does not even take environment option so the current documentation is super wrong :stuck_out_tongue:

@timriley sorry for tagging but don’t feel comfy making a decision on this on my own, and don’t want to waste time on making a PR that would be quickly rejected :stuck_out_tongue:

This would be an inappropriate usage of RACK_ENV; see this discussion for more context. TL;DR RACK_ENV was only ever meant to define the default middleware, and it had entirely different expectations from something like HANAMI_ENV.

The framework-agnostic variable suggested there is APP_ENV.

Looking at my production deployments, I’ve done as the code comment suggested:

CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]

Okey, thank you for providing the context.
So I guess the best approach (for now) would be to update the documentation with the approach suggesting using puma more directly as the current docs are misleading and suggest using RACK_ENV which shouldn’t be used for this.

I could do that, will try to in couple of days but wondering if allowing the hanami server to also run production server would eventually be something worth considering? I don’t see a reason why such an option wouldn’t be provided? All web frameworks I know do provide a rather easy way/command to simply run the server in production env.

1 Like

This sounds good to me, thanks @krzykamil!

I would welcome this!

For what it’s worth, I wasn’t aware of the original purpose of RACK_ENV until very recently.

How we handle it in Hanami is a little different. It was there mostly as a fallback to check in case HANAMI_ENV is not present. The reason for this is that Hanami 2 evolved out of a proto-framework that itself used (mistakenly, I suppose) RACK_ENV to specify its overall app environment.

To be honest, I don’t think many people would be depending on RACK_ENV for specifying a Hanami env. I think we could remove support for it altogether, so no one gets confused.

Provide more accurate info about running hanami in prod by krzykamil · Pull Request #299 · hanami/guides · GitHub here is the guides update, I hope I make it clear enough

Hi @krzykamil! Before considering this documentation PR, I wanted to take an extra moment to verify the behaviour.

To do this, I created a fresh Hanami app, threw an action into app/actions/env.rb:

module FreshApp
  module Actions
    class Env < FreshApp::Action
      def handle(request, response)
        response.body = "Hanami is in #{Hanami.env} mode"
      end
    end
  end
end

Chucked it into the routes:

module FreshApp
  class Routes < Hanami::Routes
    root to: "env"
  end
end

And then I fired up the server. First in the default mode, which should be development:

$ bundle exec hanami server
$ curl http://localhost:2300
Hanami is in development mode

So yep, that checks out.

Now I set the env to production using HANAMI_ENV=production. Still using hanami server:

$ HANAMI_ENV=production bundle exec hanami server
$ curl http://localhost:2300
Hanami is in production mode

So this looks like hanami server is correctly started in production mode.

This is the same when passing -e production:

$ bundle exec hanami server -e production
$ curl http://localhost:2300
Hanami is in production mode

The one strange thing is that Puma reports as being in development mode:

$ bundle exec hanami server -e production
23:23:01 - INFO - Using Guardfile at /Users/tim/Source/hanami/scratch/fresh_app/Guardfile.
DEPRECATION WARNING: Lumberjack.unit_of_work will be removed in version 2.0. Use Lumberjack::Logger#tag(unit_of_work: id) instead. Called from /Users/tim/.local/share/mise/installs/ruby/3.4.2/lib/ruby/gems/3.4.0/gems/guard-2.19.1/lib/guard/runner.rb:18:in 'Guard::Runner#run'
23:23:01 - INFO - Puma starting on port 2300 in development environment.
23:23:01 - INFO - Guard is now watching at '/Users/tim/Source/hanami/scratch/fresh_app'
Puma starting in single mode...
* Puma version: 6.6.0 ("Return to Forever")
* Ruby version: ruby 3.4.2 (2025-02-15 revision d2930f8e7a) +PRISM [arm64-darwin24]
*  Min threads: 5
*  Max threads: 5
*  Environment: development.  <======= ****THIS BIT HERE****
*          PID: 15893
* Listening on http://0.0.0.0:2300
* Starting control server on http://127.0.0.1:9293
* Starting control server on http://[::1]:9293
Use Ctrl-C to stop

Perhaps that was the thing that was throwing you off? Because otherwise, the Hanami app itself appears to be correctly in production whenever we tell it to be.

The output from Puma would be because it only checks a few conventional env vars:

    def puma_options_from_env(env = ENV)
      min = env['PUMA_MIN_THREADS'] || env['MIN_THREADS']
      max = env['PUMA_MAX_THREADS'] || env['MAX_THREADS']
      workers = if env['WEB_CONCURRENCY'] == 'auto'
        require_processor_counter
        ::Concurrent.available_processor_count
      else
        env['WEB_CONCURRENCY']
      end

      {
        min_threads: min && min != "" && Integer(min),
        max_threads: max && max != "" && Integer(max),
        workers: workers && workers != "" && Integer(workers),
        environment: env['APP_ENV'] || env['RACK_ENV'] || env['RAILS_ENV'],
      }
    end

However, in our default config/puma.rb, we do instruct it to use HANAMI_ENV as the environment:

#
# Environment and port
#
port ENV.fetch("HANAMI_PORT", 2300)
environment ENV.fetch("HANAMI_ENV", "development")

So perhaps this is where the real bug is? I wonder why this isn’t getting through?

Even when I set it to production explicitly, it’s being ignored:

environment "production"

Running hanami server after this still shows “Environment: development”.

A-hah. It’s a guard thing. If we run hanami server --no-code-reloading, then the environment works as expected.

$ HANAMI_ENV=production be hanami server --no-code-reloading
Puma starting in single mode...
* Puma version: 6.6.0 ("Return to Forever")
* Ruby version: ruby 3.4.2 (2025-02-15 revision d2930f8e7a) +PRISM [arm64-darwin24]
*  Min threads: 5
*  Max threads: 5
*  Environment: production
*          PID: 16196
* Listening on http://0.0.0.0:2300
Use Ctrl-C to stop
$ bundle exec hanami server --no-code-reloading -e production
Puma starting in single mode...
* Puma version: 6.6.0 ("Return to Forever")
* Ruby version: ruby 3.4.2 (2025-02-15 revision d2930f8e7a) +PRISM [arm64-darwin24]
*  Min threads: 5
*  Max threads: 5
*  Environment: production
*          PID: 16218
* Listening on http://0.0.0.0:2300
Use Ctrl-C to stop

When bundling an app for deployment, the hanami-reloader gem should not be in the bundle, because we put that in the :development bundler group.

So this makes me think that perhaps we don’t need to update our documentation at all. It seems like things are working properly.

The one thing that would be worth fixing is figuring out why guard-puma is forcing development mode at all times, and preventing it from doing that.

In fact, this might just be a matter of adding a few lines to our default Guardfile:

group :server do
  guard "puma", port: ENV.fetch("HANAMI_PORT", 2300), environment: ENV.fetch("HANAMI_ENV", "development") do
    # Edit the following regular expression for your needs.
    # See: https://guides.hanamirb.org/app/code-reloading/
    watch(%r{^(app|config|lib|slices)([\/][^\/]+)*.(rb|erb|haml|slim)$}i)
  end
end

I added environment: ENV.fetch("HANAMI_ENV", "development") there and everything now works as expected.

Yea the problem was never Hanami app environment not being sent. I realized I missed to copy this info here from my initial discord message, it was just the server.

hanami server --no-code-reloading this being the requirement to run the server in production is not very intuitive, so to my understanding the question is should we do something to have hanami server -e production apply no code reloading automatically or rather change the default puma guard we generate?

Right, I understand you now, @krzykamil! Yes, I reckon we should make it so that hanami server skips the code reloading if the env is production.

This would all happen inside the Hanami Reloader gem and its CLI extensions. In addition to skipping the reloading, it should also emit a warning where we recommend that the hanami-reloader gem be kept out of the production bundle, since it is not needed there.

How’s that sound?

1 Like

Yea that makes more sense. So do you think any documentation update is needed with those things? Does not seem like it? hanami server will be more descriptive on its own

Yep, I think if we make these changes, our docs can stay as they are! (Which is think is a good outcome, they already read pretty sensibly to me).