Skip to content

RubyGems Guide

Artifact Keeper provides a fully compatible RubyGems repository for publishing and installing Ruby gems.

Endpoint

All RubyGems operations use the /gems endpoint:

http://localhost:8080/gems/

Configuration

gem Configuration

Configure gem to use Artifact Keeper as a source:

Terminal window
gem sources --add http://localhost:8080/gems/

List configured sources:

Terminal window
gem sources --list

Remove default RubyGems.org (optional):

Terminal window
gem sources --remove https://rubygems.org/

~/.gemrc Configuration

Create or edit ~/.gemrc in your home directory:

---
:sources:
- http://localhost:8080/gems/
:backtrace: false
:bulk_threshold: 1000
:verbose: true

Bundler Configuration

Gemfile Source

Configure source in your Gemfile:

source 'http://localhost:8080/gems/'
gem 'rails', '~> 7.0'
gem 'pg', '~> 1.5'

Multiple Sources

Use different sources for different gems:

source 'http://localhost:8080/gems/'
# Specific gem from different source
gem 'private-gem', source: 'http://localhost:8080/gems/'
# Or use a block for multiple gems
source 'http://localhost:8080/gems/' do
gem 'internal-auth'
gem 'internal-logger'
end

Bundle Config

Configure Bundler globally:

Terminal window
bundle config set --global mirror.https://rubygems.org http://localhost:8080/gems/

Or per-project:

Terminal window
bundle config set --local mirror.https://rubygems.org http://localhost:8080/gems/

Authentication

Option 1: Credentials File

Create or edit ~/.gem/credentials:

---
:artifact_keeper: http://username:password@localhost:8080/gems/

Set proper permissions:

Terminal window
chmod 0600 ~/.gem/credentials

Option 2: Environment Variables

Set credentials via environment:

Terminal window
export GEM_HOST_API_KEY=your-api-key

Option 3: URL Authentication

Include credentials in the source URL:

Terminal window
gem sources --add http://username:password@localhost:8080/gems/

In Gemfile:

source 'http://username:password@localhost:8080/gems/'

Option 4: Bundle Config Credentials

Configure credentials for Bundler:

Terminal window
bundle config set --global gems.localhost:8080 username:password

Publishing Gems

Prepare Your Gem

Create a gemspec file (my_gem.gemspec):

Gem::Specification.new do |spec|
spec.name = "my_gem"
spec.version = "1.0.0"
spec.authors = ["Your Name"]
spec.email = ["you@example.com"]
spec.summary = "Short summary of your gem"
spec.description = "Longer description of your gem"
spec.homepage = "https://github.com/username/my_gem"
spec.license = "MIT"
spec.required_ruby_version = ">= 2.7.0"
spec.files = Dir["lib/**/*", "README.md", "LICENSE.txt"]
spec.require_paths = ["lib"]
# Dependencies
spec.add_dependency "rails", "~> 7.0"
# Development dependencies
spec.add_development_dependency "rspec", "~> 3.12"
end

Build Your Gem

Build the gem package:

Terminal window
gem build my_gem.gemspec

This creates my_gem-1.0.0.gem.

Push to Repository

Push using gem command:

Terminal window
gem push my_gem-1.0.0.gem --host http://localhost:8080/gems/

Or with credentials key:

Terminal window
gem push my_gem-1.0.0.gem --key artifact_keeper

Rake Tasks

Automate building and pushing with Rake:

# Rakefile
require 'bundler/gem_tasks'
desc 'Build and push gem to Artifact Keeper'
task :publish do
system('gem build my_gem.gemspec') || exit(1)
system('gem push my_gem-*.gem --host http://localhost:8080/gems/') || exit(1)
end

Run with:

Terminal window
rake publish

Installing Gems

Install with gem

Install latest version:

Terminal window
gem install my_gem --source http://localhost:8080/gems/

Install specific version:

Terminal window
gem install my_gem -v 1.0.0 --source http://localhost:8080/gems/

Install pre-release version:

Terminal window
gem install my_gem --pre --source http://localhost:8080/gems/

Install with Bundler

Add to Gemfile:

source 'http://localhost:8080/gems/'
gem 'my_gem'
gem 'another_gem', '~> 2.0'
gem 'exact_gem', '1.5.0'

Install all gems:

Terminal window
bundle install

Update specific gem:

Terminal window
bundle update my_gem

Update all gems:

Terminal window
bundle update

Install from Git

Bundler also supports installing from Git:

gem 'my_gem', git: 'https://github.com/username/my_gem.git'
gem 'my_gem', git: 'https://github.com/username/my_gem.git', tag: 'v1.0.0'
gem 'my_gem', git: 'https://github.com/username/my_gem.git', branch: 'develop'

Version Management

Semantic Versioning

Follow semantic versioning in your gemspec:

lib/my_gem/version.rb
module MyGem
VERSION = "1.0.0"
end
# my_gem.gemspec
require_relative 'lib/my_gem/version'
Gem::Specification.new do |spec|
spec.version = MyGem::VERSION
end

Version Constraints

In Gemfile, use version constraints:

# Exact version
gem 'my_gem', '1.0.0'
# Pessimistic operator (recommended)
gem 'my_gem', '~> 1.0' # >= 1.0.0, < 2.0.0
gem 'my_gem', '~> 1.0.5' # >= 1.0.5, < 1.1.0
# Comparison operators
gem 'my_gem', '>= 1.0.0'
gem 'my_gem', '> 1.0', '< 2.0'

Pre-release Versions

Create pre-release versions:

my_gem.gemspec
spec.version = "1.0.0.beta.1"
spec.version = "1.0.0.rc.1"

Install pre-release:

Terminal window
gem install my_gem --pre

In Gemfile:

gem 'my_gem', '>= 1.0.0.beta.1'

Platform-Specific Gems

Build for specific platforms:

Terminal window
gem build my_gem.gemspec --platform ruby
gem build my_gem.gemspec --platform java

In Gemfile:

gem 'my_gem', platforms: :ruby
gem 'my_gem', platforms: :jruby
gem 'my_gem', platforms: [:mswin, :mingw]

Bundler Deep Dive

Gemfile.lock

Bundler generates Gemfile.lock to lock exact versions:

Terminal window
bundle install # Creates/updates Gemfile.lock

Commit Gemfile.lock for applications, but not for gems.

Bundle Package

Vendor all gems locally:

Terminal window
bundle package

Install from vendored cache:

Terminal window
bundle install --local

Bundle Deployment

Deploy to production:

Terminal window
bundle config set --local deployment true
bundle install

This ensures:

  • Uses exact versions from Gemfile.lock
  • Fails if Gemfile.lock is out of sync
  • Installs to vendor/bundle

Bundle Exec

Run commands with gem versions from Gemfile:

Terminal window
bundle exec rails console
bundle exec rake db:migrate
bundle exec rspec

Bundle Outdated

Check for outdated gems:

Terminal window
bundle outdated
bundle outdated my_gem

Integration with CI/CD

GitHub Actions

name: Publish Gem
on:
push:
tags:
- 'v*'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true
- name: Build gem
run: gem build *.gemspec
- name: Publish gem
run: |
mkdir -p ~/.gem
cat > ~/.gem/credentials << EOF
---
:artifact_keeper: http://${{ secrets.GEM_USERNAME }}:${{ secrets.GEM_PASSWORD }}@localhost:8080/gems/
EOF
chmod 0600 ~/.gem/credentials
gem push *.gem --key artifact_keeper

GitLab CI

publish:
image: ruby:3.3
script:
- mkdir -p ~/.gem
- |
cat > ~/.gem/credentials << EOF
---
:artifact_keeper: http://${GEM_USERNAME}:${GEM_PASSWORD}@localhost:8080/gems/
EOF
- chmod 0600 ~/.gem/credentials
- gem build *.gemspec
- gem push *.gem --key artifact_keeper
only:
- tags

Jenkins Pipeline

pipeline {
agent any
tools {
ruby 'Ruby 3.3'
}
stages {
stage('Publish Gem') {
steps {
withCredentials([
usernamePassword(
credentialsId: 'gem-credentials',
usernameVariable: 'GEM_USERNAME',
passwordVariable: 'GEM_PASSWORD'
)
]) {
sh '''
mkdir -p ~/.gem
cat > ~/.gem/credentials << EOF
---
:artifact_keeper: http://${GEM_USERNAME}:${GEM_PASSWORD}@localhost:8080/gems/
EOF
chmod 0600 ~/.gem/credentials
gem build *.gemspec
gem push *.gem --key artifact_keeper
'''
}
}
}
}
}

CircleCI

version: 2.1
jobs:
publish:
docker:
- image: cimg/ruby:3.3
steps:
- checkout
- run:
name: Configure credentials
command: |
mkdir -p ~/.gem
cat > ~/.gem/credentials << EOF
---
:artifact_keeper: http://${GEM_USERNAME}:${GEM_PASSWORD}@localhost:8080/gems/
EOF
chmod 0600 ~/.gem/credentials
- run:
name: Build and publish
command: |
gem build *.gemspec
gem push *.gem --key artifact_keeper
workflows:
publish-gem:
jobs:
- publish:
filters:
tags:
only: /^v.*/
branches:
ignore: /.*/

Advanced Configuration

Multiple Repositories

Use different repositories for different gems:

source 'http://localhost:8080/gems/'
source 'http://team-gems:8080/gems/' do
gem 'team-internal'
end
source 'https://rubygems.org/' do
gem 'rails'
end

SSL/TLS Configuration

For HTTPS endpoints with self-signed certificates:

Terminal window
bundle config set --global ssl_verify_mode 0

Or specify CA certificate:

Terminal window
bundle config set --global ssl_ca_cert /path/to/ca-cert.pem

HTTP Proxy

Configure HTTP proxy for gem operations:

Terminal window
export HTTP_PROXY=http://proxy.example.com:8080
export HTTPS_PROXY=http://proxy.example.com:8080

Or in ~/.gemrc:

---
http_proxy: http://proxy.example.com:8080

Gem Installation Directory

Install gems to custom directory:

Terminal window
gem install my_gem --install-dir /custom/path

Or set default:

Terminal window
export GEM_HOME=/custom/path
export GEM_PATH=/custom/path

Gem Development

Creating a New Gem

Generate gem skeleton:

Terminal window
bundle gem my_gem

This creates:

my_gem/
├── Gemfile
├── Rakefile
├── my_gem.gemspec
├── lib/
│ └── my_gem.rb
└── spec/
└── my_gem_spec.rb

Testing Your Gem

Install gem locally for testing:

Terminal window
gem build my_gem.gemspec
gem install ./my_gem-1.0.0.gem

Or use Bundler:

# In another project's Gemfile
gem 'my_gem', path: '/path/to/my_gem'

Yanking a Gem

Remove a published version (not recommended):

Terminal window
gem yank my_gem -v 1.0.0 --host http://localhost:8080/gems/

Troubleshooting

Authentication Errors

Verify credentials:

Terminal window
cat ~/.gem/credentials

Test connection:

Terminal window
gem list --remote --source http://localhost:8080/gems/

Publishing Failures

Check gem validity:

Terminal window
gem build my_gem.gemspec
gem specification my_gem-1.0.0.gem

Enable verbose output:

Terminal window
gem push my_gem-1.0.0.gem --host http://localhost:8080/gems/ --verbose

Installation Issues

Clear gem cache:

Terminal window
gem cleanup
gem environment

For Bundler issues:

Terminal window
bundle clean --force
rm -rf vendor/bundle
rm Gemfile.lock
bundle install

Version Conflicts

View dependency tree:

Terminal window
bundle viz
bundle list

Check for conflicts:

Terminal window
bundle exec gem dependency my_gem

Source Priority

Bundler uses sources in order. First source wins:

# Rails will come from localhost:8080
source 'http://localhost:8080/gems/'
source 'https://rubygems.org/'
gem 'rails'

Platform Issues

Specify platform explicitly:

Terminal window
bundle lock --add-platform x86_64-linux
bundle lock --add-platform ruby

Best Practices

Semantic Versioning

Follow semantic versioning:

  • Major (1.0.0 -> 2.0.0): Breaking API changes
  • Minor (1.0.0 -> 1.1.0): New features, backward compatible
  • Patch (1.0.0 -> 1.0.1): Bug fixes

Gemspec Best Practices

Include complete metadata:

Gem::Specification.new do |spec|
spec.name = "my_gem"
spec.version = MyGem::VERSION
spec.authors = ["Your Name"]
spec.email = ["you@example.com"]
spec.summary = "Brief one-line summary"
spec.description = "Longer multi-line description"
spec.homepage = "https://github.com/username/my_gem"
spec.license = "MIT"
spec.metadata = {
"homepage_uri" => spec.homepage,
"source_code_uri" => "https://github.com/username/my_gem",
"changelog_uri" => "https://github.com/username/my_gem/blob/main/CHANGELOG.md",
"bug_tracker_uri" => "https://github.com/username/my_gem/issues",
"documentation_uri" => "https://rubydoc.info/gems/my_gem"
}
spec.required_ruby_version = ">= 2.7.0"
spec.files = Dir.chdir(File.expand_path(__dir__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
end
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
end

.gitignore for Gems

Exclude build artifacts:

*.gem
*.rbc
.bundle
.config
coverage
Gemfile.lock
pkg/
spec/reports
tmp/
vendor/bundle

Version Constraints in Gemspec

Use conservative constraints:

# Runtime dependencies
spec.add_dependency "activerecord", ">= 6.0", "< 8.0"
# Development dependencies
spec.add_development_dependency "rspec", "~> 3.12"
spec.add_development_dependency "rubocop", "~> 1.50"

Gem Naming

Follow Ruby naming conventions:

  • Use underscores for multi-word gems: my_awesome_gem
  • Module names use CamelCase: MyAwesomeGem
  • Avoid conflicts with standard library

See Also