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:
gem sources --add http://localhost:8080/gems/List configured sources:
gem sources --listRemove default RubyGems.org (optional):
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: trueBundler 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 sourcegem 'private-gem', source: 'http://localhost:8080/gems/'
# Or use a block for multiple gemssource 'http://localhost:8080/gems/' do gem 'internal-auth' gem 'internal-logger'endBundle Config
Configure Bundler globally:
bundle config set --global mirror.https://rubygems.org http://localhost:8080/gems/Or per-project:
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:
chmod 0600 ~/.gem/credentialsOption 2: Environment Variables
Set credentials via environment:
export GEM_HOST_API_KEY=your-api-keyOption 3: URL Authentication
Include credentials in the source URL:
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:
bundle config set --global gems.localhost:8080 username:passwordPublishing 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"endBuild Your Gem
Build the gem package:
gem build my_gem.gemspecThis creates my_gem-1.0.0.gem.
Push to Repository
Push using gem command:
gem push my_gem-1.0.0.gem --host http://localhost:8080/gems/Or with credentials key:
gem push my_gem-1.0.0.gem --key artifact_keeperRake Tasks
Automate building and pushing with Rake:
# Rakefilerequire '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)endRun with:
rake publishInstalling Gems
Install with gem
Install latest version:
gem install my_gem --source http://localhost:8080/gems/Install specific version:
gem install my_gem -v 1.0.0 --source http://localhost:8080/gems/Install pre-release version:
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:
bundle installUpdate specific gem:
bundle update my_gemUpdate all gems:
bundle updateInstall 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:
module MyGem VERSION = "1.0.0"end
# my_gem.gemspecrequire_relative 'lib/my_gem/version'
Gem::Specification.new do |spec| spec.version = MyGem::VERSIONendVersion Constraints
In Gemfile, use version constraints:
# Exact versiongem 'my_gem', '1.0.0'
# Pessimistic operator (recommended)gem 'my_gem', '~> 1.0' # >= 1.0.0, < 2.0.0gem 'my_gem', '~> 1.0.5' # >= 1.0.5, < 1.1.0
# Comparison operatorsgem 'my_gem', '>= 1.0.0'gem 'my_gem', '> 1.0', '< 2.0'Pre-release Versions
Create pre-release versions:
spec.version = "1.0.0.beta.1"spec.version = "1.0.0.rc.1"Install pre-release:
gem install my_gem --preIn Gemfile:
gem 'my_gem', '>= 1.0.0.beta.1'Platform-Specific Gems
Build for specific platforms:
gem build my_gem.gemspec --platform rubygem build my_gem.gemspec --platform javaIn Gemfile:
gem 'my_gem', platforms: :rubygem 'my_gem', platforms: :jrubygem 'my_gem', platforms: [:mswin, :mingw]Bundler Deep Dive
Gemfile.lock
Bundler generates Gemfile.lock to lock exact versions:
bundle install # Creates/updates Gemfile.lockCommit Gemfile.lock for applications, but not for gems.
Bundle Package
Vendor all gems locally:
bundle packageInstall from vendored cache:
bundle install --localBundle Deployment
Deploy to production:
bundle config set --local deployment truebundle installThis ensures:
- Uses exact versions from
Gemfile.lock - Fails if
Gemfile.lockis out of sync - Installs to
vendor/bundle
Bundle Exec
Run commands with gem versions from Gemfile:
bundle exec rails consolebundle exec rake db:migratebundle exec rspecBundle Outdated
Check for outdated gems:
bundle outdatedbundle outdated my_gemIntegration with CI/CD
GitHub Actions
name: Publish Gemon: 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_keeperGitLab 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: - tagsJenkins 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'endSSL/TLS Configuration
For HTTPS endpoints with self-signed certificates:
bundle config set --global ssl_verify_mode 0Or specify CA certificate:
bundle config set --global ssl_ca_cert /path/to/ca-cert.pemHTTP Proxy
Configure HTTP proxy for gem operations:
export HTTP_PROXY=http://proxy.example.com:8080export HTTPS_PROXY=http://proxy.example.com:8080Or in ~/.gemrc:
---http_proxy: http://proxy.example.com:8080Gem Installation Directory
Install gems to custom directory:
gem install my_gem --install-dir /custom/pathOr set default:
export GEM_HOME=/custom/pathexport GEM_PATH=/custom/pathGem Development
Creating a New Gem
Generate gem skeleton:
bundle gem my_gemThis creates:
my_gem/ ├── Gemfile ├── Rakefile ├── my_gem.gemspec ├── lib/ │ └── my_gem.rb └── spec/ └── my_gem_spec.rbTesting Your Gem
Install gem locally for testing:
gem build my_gem.gemspecgem install ./my_gem-1.0.0.gemOr use Bundler:
# In another project's Gemfilegem 'my_gem', path: '/path/to/my_gem'Yanking a Gem
Remove a published version (not recommended):
gem yank my_gem -v 1.0.0 --host http://localhost:8080/gems/Troubleshooting
Authentication Errors
Verify credentials:
cat ~/.gem/credentialsTest connection:
gem list --remote --source http://localhost:8080/gems/Publishing Failures
Check gem validity:
gem build my_gem.gemspecgem specification my_gem-1.0.0.gemEnable verbose output:
gem push my_gem-1.0.0.gem --host http://localhost:8080/gems/ --verboseInstallation Issues
Clear gem cache:
gem cleanupgem environmentFor Bundler issues:
bundle clean --forcerm -rf vendor/bundlerm Gemfile.lockbundle installVersion Conflicts
View dependency tree:
bundle vizbundle listCheck for conflicts:
bundle exec gem dependency my_gemSource Priority
Bundler uses sources in order. First source wins:
# Rails will come from localhost:8080source 'http://localhost:8080/gems/'source 'https://rubygems.org/'
gem 'rails'Platform Issues
Specify platform explicitly:
bundle lock --add-platform x86_64-linuxbundle lock --add-platform rubyBest 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.configcoverageGemfile.lockpkg/spec/reportstmp/vendor/bundleVersion Constraints in Gemspec
Use conservative constraints:
# Runtime dependenciesspec.add_dependency "activerecord", ">= 6.0", "< 8.0"
# Development dependenciesspec.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
- Security Scanning - Automatic vulnerability scanning for Ruby gems
- Security Policies - Configure policies to block vulnerable gems