Skip to content

Commit 8b8f957

Browse files
committed
Adds another example: A simple Ruby web app
1 parent 50f96b5 commit 8b8f957

File tree

10 files changed

+205
-1
lines changed

10 files changed

+205
-1
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,5 @@ To understand best practice regarding building a `Dockerfile` and thus container
6363

6464
Enough with all the theory - we have fully documented and working examples that you can use as reference on getting started with Docker integration. All examples are selected so that minimal code is needed.
6565

66-
* **[Logstash](example-logstash/)** - A fully working example that creates a custom logstash image that consumes the heise.de RSS feed, puts it into Elasticsearch and makes it searchable using Kibana
66+
* **[Logstash](example-logstash/)** - A fully working example that creates a custom logstash image that consumes the heise.de RSS feed, puts it into Elasticsearch and makes it searchable using Kibana
67+
* **[Ruby Web App](example-ruby-webapp/)** - Shows how to use containerize a Ruby web application using the community-provided [Ruby docker base image](https://hub.docker.com/_/ruby/)

example-ruby-webapp/README.md

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Platform Integration - Ruby Web app example
2+
3+
This example shows how to wrap a web application written in Ruby in a Docker container using the community-provided
4+
[Ruby docker base image](https://hub.docker.com/_/ruby/) and hence should give you a basic overview of how to
5+
wrap a software application in a docker image based on the existing public language stack images.
6+
7+
The `docker-compose.yml` and `Dockerfile` include comments on what is happening.
8+
9+
## What does it do?
10+
11+
The app directory contains an example ruby app that acts as a caching proxy: It fetches data from another HTTP
12+
api, caching the response for 30 seconds inside redis because our remote API is slow - for the sake of this demo
13+
we are using a special HTTP service that intentionally responds very slowly.
14+
15+
## Running it
16+
17+
On your local checkout of this repository, open this directory in your terminal, then launch:
18+
19+
docker-compose up
20+
21+
This should build the app image, pull the redis image and launch both.
22+
23+
You can now `curl localhost`. The first request should take around two seconds since the response is not cached in
24+
redis yet, subsequent requests should be screaming fast thanks to the cached response stored in our redis container.
25+
After 30 seconds the cache expires and will be fetched from the upstream server again.
26+
27+
## Stopping it
28+
29+
You can hit `ctrl+c` in your terminal to stop the running services, then you can remove the local containers:
30+
31+
docker-compose rm -f
32+
33+
## Development flow
34+
35+
Running `docker-compose build` will re-build your image as you are putting together your Dockerfile.
36+
37+
Once the image is built, you can also execute the unit tests for the app inside a docker container using:
38+
39+
docker run -i -t --rm examplerubywebapp_app bundle exec rspec spec.rb
40+
41+
This can also be helpful for setting up continuous integration for your image repository. On a CI server, on each
42+
push to your source code repository you could:
43+
44+
1. Build the image using `docker build --pull -t myrepo/myapp:latest .`
45+
2. Run your test suite inside the container to verify it works: `docker run -i -t --rm myrepo/myapp:latest bundle exec rspec spec.rb`
46+
3. Push the image to your registry repo on success: `docker push myrepo/myapp:latest`

example-ruby-webapp/app/.rspec

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--colour --format documentation

example-ruby-webapp/app/Dockerfile

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Use the onbuild ruby language image that installs our dependencies
2+
# and copies the app source automatically.
3+
# See https://hub.docker.com/_/ruby/
4+
FROM ruby:2.3-onbuild
5+
6+
# Set environment variables used by our app for configuration
7+
ENV RACK_ENV production
8+
ENV REDIS_URL redis://redis:6379
9+
10+
# Open up port 5000 for consumption by other containers
11+
EXPOSE 5000
12+
13+
# The default command to execute when running this container
14+
CMD ["bundle", "exec", "puma", "-p", "5000"]

example-ruby-webapp/app/Gemfile

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
source 'https://rubygems.org'
2+
3+
gem 'sinatra'
4+
gem 'redis'
5+
gem 'httpclient'
6+
gem 'puma', require: false
7+
8+
group :test do
9+
gem 'rspec'
10+
gem 'rack-test'
11+
end

example-ruby-webapp/app/Gemfile.lock

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
GEM
2+
remote: https://rubygems.org/
3+
specs:
4+
diff-lcs (1.2.5)
5+
httpclient (2.7.1)
6+
puma (2.15.3)
7+
rack (1.6.4)
8+
rack-protection (1.5.3)
9+
rack
10+
rack-test (0.6.3)
11+
rack (>= 1.0)
12+
redis (3.2.2)
13+
rspec (3.4.0)
14+
rspec-core (~> 3.4.0)
15+
rspec-expectations (~> 3.4.0)
16+
rspec-mocks (~> 3.4.0)
17+
rspec-core (3.4.1)
18+
rspec-support (~> 3.4.0)
19+
rspec-expectations (3.4.0)
20+
diff-lcs (>= 1.2.0, < 2.0)
21+
rspec-support (~> 3.4.0)
22+
rspec-mocks (3.4.1)
23+
diff-lcs (>= 1.2.0, < 2.0)
24+
rspec-support (~> 3.4.0)
25+
rspec-support (3.4.1)
26+
sinatra (1.4.6)
27+
rack (~> 1.4)
28+
rack-protection (~> 1.4)
29+
tilt (>= 1.3, < 3)
30+
tilt (2.0.2)
31+
32+
PLATFORMS
33+
ruby
34+
35+
DEPENDENCIES
36+
httpclient
37+
puma
38+
rack-test
39+
redis
40+
rspec
41+
sinatra
42+
43+
BUNDLED WITH
44+
1.11.2

example-ruby-webapp/app/app.rb

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
ENV["RACK_ENV"] ||= "development"
2+
require "bundler"
3+
Bundler.require :default, ENV["RACK_ENV"]
4+
require 'digest/sha1'
5+
6+
class App < Sinatra::Base
7+
helpers do
8+
# Initializes memoized redis client instance
9+
def redis
10+
@redis ||= Redis.new url: ENV["REDIS_URL"]
11+
end
12+
13+
# Converts given string into a shorter cache key
14+
def cache_key(string)
15+
Digest::SHA1.hexdigest string
16+
end
17+
18+
# Try to fetch the given url from redis cache, otherwise request it and store
19+
# it in cache for 30 seconds
20+
def try_cached(url)
21+
if cached = redis.get(cache_key(url))
22+
cached
23+
else
24+
response_body = HTTPClient.get(url).body
25+
redis.set cache_key(url), response_body
26+
redis.expire cache_key(url), 30
27+
response_body
28+
end
29+
end
30+
end
31+
32+
get "/" do
33+
# Fetches response from a slow response fake http web service. Subsequent requests
34+
# should be fast thanks to our redis cache until the cache key expires.
35+
try_cached 'http://fake-response.appspot.com/?sleep=2'
36+
end
37+
end

example-ruby-webapp/app/config.ru

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require './app'
2+
run App

example-ruby-webapp/app/spec.rb

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
ENV['RACK_ENV'] = 'test'
2+
require './app'
3+
4+
RSpec.describe App do
5+
include Rack::Test::Methods
6+
let(:app) { App }
7+
8+
let(:url) { "http://fake-response.appspot.com/?sleep=2" }
9+
let(:cache_key) { Digest::SHA1.hexdigest url }
10+
11+
let(:redis) { double("Redis") }
12+
13+
before(:each) do
14+
# Mock out our Redis client
15+
allow(Redis).to receive(:new).and_return(redis)
16+
end
17+
18+
it "queries the HTTP endpoint when not cached, storing it in cache" do
19+
expect(HTTPClient).to receive(:get).with(url).and_return(double("response", body: 'The Response'))
20+
expect(redis).to receive(:get).with(cache_key).and_return(nil)
21+
expect(redis).to receive(:set).with(cache_key, 'The Response')
22+
expect(redis).to receive(:expire).with(cache_key, 30)
23+
24+
get '/'
25+
expect(last_response.body).to be == 'The Response'
26+
end
27+
28+
it "uses the cached value and does not query HTTP when cached" do
29+
expect(HTTPClient).not_to receive(:get)
30+
expect(redis).to receive(:get).with(cache_key).and_return('Cached Response')
31+
32+
get '/'
33+
expect(last_response.body).to be == 'Cached Response'
34+
end
35+
end
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
app:
2+
# We want docker to build the image based upon the Dockerfile in the app directory
3+
build: app
4+
# We want to allow access to our running redis service
5+
links:
6+
- redis
7+
# Expose our app server on localhost
8+
ports:
9+
- "80:5000"
10+
11+
redis:
12+
# Just fire up the default redis image as a container
13+
image: redis

0 commit comments

Comments
 (0)