Gem in a BoxでプライベートなRubyGemsをDockerで簡単に構築する

Gem in a Boxを使って、Dockerでローカル環境にRubyGemsを構築して、Gemをアップロードするところまで書きます。

github.com

Gem in a Boxは公式でも以下の RUNNING GEM IN A BOX の項目で紹介されてます。

Run your own gem server - RubyGems Guides

作業前提

この手順を実行するために必要なのは、DockerとGitだけです。

  • Docker
  • Git

テスト用のGem作ってアップロードする手順にしているので、Gem作る環境ではRubyのインストールが必要です。

Dockerfile作成

ワークディレクトリ作成して、Dockerfileを作成します。 Dockerfileはgeminaboxのリポジトリの中のものと同じです。

https://github.com/geminabox/geminabox/blob/master/Dockerfile

WORK_DIR=./work
mkdir ${WORK_DIR} &&  cd ${WORK_DIR}/
cat <<EOF >./Dockerfile
FROM ruby:2.4.3

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

COPY . /usr/src/app
RUN bundle install

EXPOSE 9292

ENTRYPOINT ["rackup", "--host", "0.0.0.0"]
EOF

設定ファイル準備(config.ru)

geminaboxの設定ファイルを準備します。

ここでは、GithubのWiki上にあった内容を調整して、アップロートと削除にBasic認証入れる方式を設定してます。

DATA_DIR=/data/gems
USERNAME='user'
PASSWORD='Passw0rd'


cat <<EOF > ./config.ru
require "rubygems"
require "geminabox"

Geminabox.data = "${DATA_DIR}"

Geminabox::Server.helpers do
  def protected!
    unless authorized?
      response['WWW-Authenticate'] = %(Basic realm="Geminabox")
      halt 401, "No pushing or deleting without auth.\n"
    end
  end

  def authorized?
    @auth ||=  Rack::Auth::Basic::Request.new(request.env)
    @auth.provided? && @auth.basic? && @auth.credentials && @auth.credentials == ['${USERNAME}', '${PASSWORD}']
  end
end

Geminabox::Server.before '/upload' do
  protected!
end

Geminabox::Server.before do
  protected! if request.delete?
end

use Rack::Session::Pool, expire_after: 1000 # sec
use Rack::Protection

run Geminabox::Server
EOF

設定ファイル準備(Gemfile)

以下の通りGemfileを準備します。

cat <<EOF > ./Gemfile
source 'https://rubygems.org'

gem 'geminabox'
EOF

Dockerイメージのビルド

ワークフォルダでイメージをビルドします。

docker build -t geminabox .

Gem in a Boxの起動

Geminabox.data = "${DATA_DIR}" でGemをボリュームに保存する設定としながら起動します。80でLISTENしてみます。

DATA_DIR=/data/gems
docker run -d -v ruby_gems:${DATA_DIR} -p 80:9292  --restart always geminabox:latest

ブラウザからアクセスして、以下のような画面が表示されることを確認します。

http://{Your IP Address or Hostname}/

f:id:yomon8:20191024180623p:plain

テスト用のGemを作成してアップロードしてみる

簡単なGemを作成してアップロードしてみます。

ここでは、Hello Worldを出力するテスト用のRuby Gemを作成します。Rubyがインストールされている環境で作業します。

Bundlerでテンプレート出力

bundlerでgemのテンプレートを出力します。

$ bundle gem my_helloworld
Creating gem 'my_helloworld'...
      create  my_helloworld/Gemfile
      create  my_helloworld/lib/my_helloworld.rb
      create  my_helloworld/lib/my_helloworld/version.rb
      create  my_helloworld/my_helloworld.gemspec
      create  my_helloworld/Rakefile
      create  my_helloworld/README.md
      create  my_helloworld/bin/console
      create  my_helloworld/bin/setup
      create  my_helloworld/.gitignore
Initializing git repo in /mnt/c/Users/seban/my_helloworld
Gem 'my_helloworld' was successfully created. For more information on making a RubyGem visit https://bundler.io/guides/creating_gem.html

テンプレート調整

テンプレートのディレクトリに移動します。

$ cd my_helloworld/

今回は以下のようなgemspecで置き換えます。

$ cat <<EOF > ./my_helloworld.gemspec
lib = File.expand_path("lib", __dir__)
\$LOAD_PATH.unshift(lib) unless \$LOAD_PATH.include?(lib)
require "my_helloworld/version"

Gem::Specification.new do |spec|
  spec.name          = "my_helloworld"
  spec.version       = MyHelloworld::VERSION
  spec.authors       = ["yomon8"]

  spec.summary       = "my_helloworld"
  spec.description   = "my_helloworld"

  spec.files         = Dir.chdir(File.expand_path('..', __FILE__)) do
    \`git ls-files -z\`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
  end
  spec.bindir        = "exe"
  spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
  spec.require_paths = ["lib"]

  spec.add_development_dependency "bundler", "~> 2.0"
  spec.add_development_dependency "rake", "~> 10.0"
end
EOF

Hello World を出力する処理にします。

cat <<EOF > ./lib/my_helloworld.rb
require "my_helloworld/version"

module MyHelloworld
  class << self
    def hello
      puts "Hello world"
    end
  end
end
EOF

Gemのビルドとアップロード

引き続き、 my_helloworld ディレクトリで作業します。

Gemをビルドします。

$ rake build
my_helloworld 0.1.0 built to pkg/my_helloworld-0.1.0.gem.

出力されたファイルを確認します。

$ ls pkg/
my_helloworld-0.1.0.gem

Gemのアップロード

f:id:yomon8:20191024175828p:plain

アップロードにはBasic認証がかかっていることがわかります。 config.ru で設定したユーザ名とパスワードでログインします。

f:id:yomon8:20191024175919p:plain

先程、ビルドしたGemファイルをアップロードします。

f:id:yomon8:20191024154320p:plain

以下のようなメッセージが表示されます。

Gem my_helloworld-0.1.0.gem received and indexed.

トップページにいくと、アップロードしたGemが確認できます。

f:id:yomon8:20191024155628p:plain

Gemを利用してみる

DockerのRubyのイメージ使って動作確認してみます。

$ docker run -it --rm ruby:alpine /bin/sh

先程のテスト用Gemをインストールしようとすると当然インストールできません。

/ # gem install my_helloworld
ERROR:  Could not find a valid gem 'my_helloworld' (>= 0) in any repository
ERROR:  Possible alternatives: helloworld, rs-helloworld, Hello__World, bj_hello_world, jb-helloworld

sourceとしてgeminaboxサーバーを追加します。

/# gem source -a http://xx.xx.xx.xx
http://xx.xx.xx.xx added to sources

これでインストールできます。

/# gem install my_helloworld
Fetching my_helloworld-0.1.0.gem
Successfully installed my_helloworld-0.1.0
1 gem installed

ちゃんと動くこと確認できました。

/# ruby -rmy_helloworld  -e 'MyHelloworld::hello'
Hello world