stk1234
stk1234

Reputation: 1126

Need config.ru to Start up a Sinatra App from within a Docker Container?

Why isn't the simple command ruby my app.rb working to boot up my Sinatra application from within a Docker container?

I have a very simple Sinatra app:

# myapp.rb

require 'sinatra'

get '/' do
  'Hello world!'
end

I run this locally with ruby myapp.rb and I get the following output

== Sinatra (v2.1.0) has taken the stage on 4567 for development with backup from Puma
Puma starting in single mode...
* Puma version: 5.1.1 (ruby 2.7.0-p0) ("At Your Service")
*  Min threads: 0
*  Max threads: 5
*  Environment: development
*          PID: 49242
* Listening on http://127.0.0.1:4567
* Listening on http://[::1]:4567
Use Ctrl-C to stop

Opens up on http://127.0.0.1:4567 with no issue. When moving to Dockerize the app, I create a Gemfile with Sinatra and the following Dockerfile.

FROM ruby:2.7.0

WORKDIR /code
COPY . /code
RUN bundle install

CMD ["ruby", "myapp.rb"]

Standing up the container, it seem successful (Docker Desktop is green, no terminal errors), but clicking on the suggested link http://localhost:4567/ doesn't load (sad Chrome face). Logs from within the container look like so

[2020-12-27 18:04:52] INFO WEBrick 1.6.0
[2020-12-27 18:04:52] INFO ruby 2.7.0 (2019-12-25) [x86_64-linux]
== Sinatra (v2.1.0) has taken the stage on 4567 for development with backup from WEBrick
[2020-12-27 18:04:52] INFO WEBrick::HTTPServer#start: pid=1 port=4567

However, when I add the below config.ru file and change the last line of my Dockerfile to CMD ["bundle", "exec", "rackup", "--host", "0.0.0.0", "-p", "4567"], http://localhost:4567/ opens with no issue.

# config.ru

require './myapp'
    
run Sinatra::Application

Why are these tweaks necessary to make the app work? The logs from with the container look nearly the same.

[2020-12-27 18:01:49] INFO WEBrick 1.6.0
[2020-12-27 18:01:49] INFO ruby 2.7.0 (2019-12-25) [x86_64-linux]
[2020-12-27 18:01:49] INFO WEBrick::HTTPServer#start: pid=1 port=4567
172.17.0.1 - - [27/Dec/2020:18:02:44 +0000] "GET / HTTP/1.1" 200 12 0.0420

I'm not necessarily wondering about "best practices" here (this is a side project). I'm more just trying to understand what I might be missing about how Dockerizing apps works.

Docker commands for both cases (and I clear the images/containers between runs):

docker build --tag sinatra-img .
docker run --name sinatra-app -dp 4567:4567 sinatra-img

Upvotes: 1

Views: 1315

Answers (2)

Myst
Myst

Reputation: 19221

NOTE:

The following answer relates to a previous version of the question. The new question has a different answer (fixing the binding address using the -o 0.0.0.0 CLI argument).


The Sinatra framework is based on Rack and requires a Rack compatible server... either that, or it can also fallback on the WEBrick server that's included with the Ruby language bundle.

WEBrick is a decent server, but it wasn't designed for the heavier loads or the needs of an actual web application running in production.

For this reason, you SHOULD use a Rack compatible server.

However, this does not mean that you have to use the rackup CLI helper.

Some servers, like Puma, iodine and passenger include their own CLI, so you could run your application using:

CMD ["bundle", "exec", "puma", "-p", "4567"]

Type puma -h (or iodine -h) for more command line options. A server's specific CLI might offer some server specific features you don't get with backup. For example, Iodine exposes some security options through it's CLI (maximum file upload size, maximum total header length, web socket message limits, etc').

Using the server's CLI interface should be considered a better option.

In addition, although I wouldn't recommend it, some servers also provide a Ruby API that allows you start the server from a Ruby script (instead of a config.ru file). i.e., with iodine (I'm biased):

ENV['PORT'] ||= "4567"
require 'iodine' # will test the `ENV['PORT']` value

require 'sinatra'

get '/' do
  'Hello world!'
end

Iodine.listen service: :http, public: './public', handler: Sinatra::Application
# Iodine.threads = 16 # or whatever.
# Iodine.workers = -2 # half the core count (negative value).
Iodine.start

I wouldn't use this approach. It tends to be more fragile and it also hardcodes both the environment and the server settings in the application.

I would just add the config.ru and use a decent server (I like iodine, but Puma is much more popular and unless you need real-time pub/sub, websockets or some specific security/performance features, popular is often safer).


EDIT (according to comment):

If what you're really looking for is to embed the command bundle exec into the Ruby script (for version control using a gemfile), you can start the script with the lines

#!/usr/bin/env ruby
require 'bundler'
Bundler.require

Or, if you don't want to use a gemfile at all (or don't require version control), you can jus start the first line with:

#!/usr/bin/env ruby

Then you can start your server directly:

CMD ["puma", "-p", "4567"]

Or, without using the server's CLI, using the example script above, run:

CMD ["my_script.rb"]

Upvotes: 0

Panic
Panic

Reputation: 2405

When you start your app with ruby myapp.rb in a Docker container, your app is listening on localhost because it is running in development mode. If your Docker server runs in a VM, you won't be able to access your app. To fix this, when you run your app in a Docker container, make sure that it is listening on 0.0.0.0: ruby myapp.rb -o 0.0.0.0

Upvotes: 3

Related Questions