Overview
Artifact management addresses the storage, versioning, and distribution of compiled binaries, packages, libraries, container images, and other build outputs produced during software development. An artifact represents any file or set of files generated by the build process that needs preservation and distribution to other stages of the software delivery pipeline.
The practice emerged from the need to decouple build processes from deployment processes. Early development workflows rebuilt software from source for each deployment, creating inconsistencies between environments and wasting computational resources. Artifact management repositories store the exact binaries deployed to production, creating a single source of truth for what runs in each environment.
Modern artifact management systems function as specialized file servers with versioning capabilities, access controls, and metadata indexing. They store artifacts produced by continuous integration systems and serve them to deployment processes, developer workstations, and other build processes that depend on them.
# Artifact metadata example
artifact = {
name: 'user-service',
version: '2.3.1',
type: 'jar',
checksum: 'sha256:a3b2c1...',
build_number: 1847,
repository: 'releases',
coordinates: 'com.example:user-service:2.3.1'
}
The relationship between source control and artifact management creates a complete audit trail. Source control tracks code changes, while artifact repositories track the built results of those changes. Each artifact links back to a specific source revision, enabling reconstruction of exactly what code produced a particular binary.
Artifact repositories differ from source control systems in critical ways. Source control optimizes for text diff operations and merge conflict resolution. Artifact repositories optimize for binary storage, checksumming, and efficient retrieval of large files. Attempting to store compiled binaries in source control creates repository bloat and slow clone operations.
Key Principles
Immutability forms the foundation of artifact management. Once published to a repository, an artifact version never changes. The file at coordinates com.example:service:1.2.3 remains identical across all time. This immutability guarantees that deployments remain reproducible - deploying version 1.2.3 today produces the same result as deploying it six months from now.
Violating immutability breaks reproducibility. If artifact 1.2.3 changes after initial publication, systems that cached the original version behave differently from systems that fetch the updated version. The version number becomes meaningless as an identifier of specific functionality.
Versioning schemes identify distinct artifact instances. Semantic versioning (major.minor.patch) indicates compatibility and change significance. Maven coordinates combine group ID, artifact ID, and version into unique identifiers: org.example:library:2.1.0. Ruby gems use name and version: rails-7.0.4.gem.
Snapshot versions represent work-in-progress artifacts. The version 1.2.3-SNAPSHOT indicates an unstable build that may change. Repositories handle snapshots differently from release versions - snapshots allow overwrites within the same version number, while releases enforce immutability. Teams use snapshots during active development and cut release versions for production deployments.
# Version comparison and constraints
class ArtifactVersion
include Comparable
attr_reader :major, :minor, :patch, :snapshot
def initialize(version_string)
parts = version_string.split('-')
@snapshot = parts.include?('SNAPSHOT')
version_parts = parts[0].split('.').map(&:to_i)
@major, @minor, @patch = version_parts
end
def <=>(other)
return major <=> other.major unless major == other.major
return minor <=> other.minor unless minor == other.minor
return patch <=> other.patch unless patch == other.patch
snapshot ? -1 : 0 # Snapshots sort before releases
end
def release?
!snapshot
end
end
# => Usage
v1 = ArtifactVersion.new('2.3.1-SNAPSHOT')
v2 = ArtifactVersion.new('2.3.1')
v1 < v2 # => true
Repository types serve different purposes in the artifact lifecycle. Release repositories store immutable production artifacts. Snapshot repositories hold development builds. Remote repositories proxy external sources like Maven Central or RubyGems, caching artifacts locally. Virtual repositories aggregate multiple repositories behind a single URL.
Checksums and signatures verify artifact integrity and authenticity. Every artifact publish generates cryptographic checksums (SHA-256, SHA-512) stored alongside the artifact. Download clients verify checksums match before using artifacts, detecting corruption or tampering. Digital signatures using GPG or similar tools prove artifacts came from trusted publishers.
Metadata and indexing enable artifact discovery. Repositories maintain indices of available artifacts, versions, and dependencies. Search operations query this metadata rather than scanning file storage. Metadata includes publication timestamps, file sizes, dependent libraries, and custom properties.
The dependency graph represents the network of artifacts that depend on each other. A web application artifact depends on dozens of library artifacts, which themselves depend on other libraries. Artifact management systems track these dependencies, enabling dependency resolution and transitive dependency analysis.
Access control restricts artifact operations based on user identity and repository policy. Anonymous users might read from public repositories but not write. Authenticated developers can publish snapshots but only automated systems can publish releases. Repository administrators control these permission mappings.
Implementation Approaches
Centralized repository architecture positions a single artifact server as the organization's primary artifact store. All build systems publish to this central location, and all deployment systems retrieve from it. This approach simplifies access control, backup procedures, and audit logging. The central repository becomes a critical infrastructure component requiring high availability.
Organizations implement centralized repositories using dedicated server instances running Artifactory, Nexus, or similar platforms. The repository server handles authentication, storage management, and API access. Build pipelines authenticate using service accounts with publish permissions. Deployment systems use read-only credentials.
# Centralized repository configuration
class ArtifactRepository
def initialize(config)
@base_url = config[:url]
@repository = config[:repository]
@credentials = config[:credentials]
@http_client = build_http_client
end
def publish(artifact)
path = artifact_path(artifact)
url = "#{@base_url}/#{@repository}/#{path}"
response = @http_client.put(url) do |req|
req.headers['Content-Type'] = artifact.content_type
req.headers['X-Checksum-Sha256'] = artifact.checksum
req.body = artifact.read
end
raise PublishError, response.body unless response.success?
{ url: url, checksum: artifact.checksum }
end
def fetch(coordinates)
path = coordinates_to_path(coordinates)
url = "#{@base_url}/#{@repository}/#{path}"
response = @http_client.get(url)
raise NotFoundError, coordinates unless response.success?
verify_checksum(response.body, response.headers['X-Checksum-Sha256'])
response.body
end
private
def artifact_path(artifact)
"#{artifact.group_id.tr('.', '/')}/#{artifact.artifact_id}/#{artifact.version}/#{artifact.filename}"
end
end
Distributed repository architecture replicates artifacts across multiple geographic regions or organizational boundaries. Each region maintains a local repository that mirrors critical artifacts, reducing network latency and improving availability. Changes propagate between repositories using replication protocols.
Distributed architectures address global development teams and multi-region deployments. Developers in Asia pull artifacts from an Asian repository instance rather than crossing oceans to reach a US-based server. Replication latency becomes a consideration - artifacts published in one region may take minutes to appear in others.
Proxy and cache patterns reduce external bandwidth and improve build reliability. A proxy repository sits between internal build systems and external artifact sources like Maven Central. When a build requests an external artifact, the proxy downloads it once and caches it locally. Subsequent requests serve from the cache.
Caching external dependencies protects against external repository outages. If Maven Central becomes unavailable, cached artifacts remain accessible. This pattern also enforces organizational policies - administrators can block specific external artifacts or require security scanning before cache entry.
Namespace partitioning organizes artifacts into logical groupings. Group IDs in Maven coordinates serve this purpose: com.example.authentication for authentication services, com.example.data for data access libraries. Ruby gems use flat namespacing but often employ prefixes: activerecord, activesupport.
Clear namespace conventions prevent naming conflicts and communicate artifact ownership. The namespace com.example.mobile immediately identifies artifacts belonging to the mobile team. Enforcing namespace permissions ensures teams publish only to their designated namespaces.
Retention policies manage repository growth over time. Snapshot repositories accumulate builds quickly - daily builds of dozens of projects create thousands of snapshot artifacts monthly. Retention policies automatically delete snapshots older than a threshold (30 days, 90 days) or keep only the latest N builds per artifact.
Release artifacts typically receive different treatment. Major versions might retain indefinitely to support legacy deployments, while minor versions expire after a year. Custom retention rules account for regulatory requirements or specific artifact importance.
Tools & Ecosystem
JFrog Artifactory provides comprehensive artifact management with support for multiple package formats. It stores Maven artifacts, Docker images, npm packages, RubyGems, and numerous other formats in a single platform. Artifactory offers both cloud-hosted and self-hosted deployment options.
Artifactory's repository types include local repositories for internal artifacts, remote repositories that proxy external sources, and virtual repositories that aggregate others. Advanced features include build integration that captures complete build metadata, promotion workflows that move artifacts between repositories, and replication across geographic regions.
The platform exposes a REST API for artifact operations:
require 'net/http'
require 'json'
class ArtifactoryClient
def initialize(url, api_key)
@url = url
@api_key = api_key
end
def upload_gem(gem_file, repository = 'gems-local')
uri = URI("#{@url}/#{repository}/#{File.basename(gem_file)}")
request = Net::HTTP::Put.new(uri)
request['X-JFrog-Art-Api'] = @api_key
request.body = File.read(gem_file)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
JSON.parse(response.body)
end
def search_artifacts(name, repository = 'gems-local')
uri = URI("#{@url}/api/search/artifact")
uri.query = URI.encode_www_form(name: name, repos: repository)
request = Net::HTTP::Get.new(uri)
request['X-JFrog-Art-Api'] = @api_key
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
JSON.parse(response.body)['results']
end
end
# => Usage
client = ArtifactoryClient.new('https://artifacts.example.com/artifactory', ENV['ARTIFACTORY_API_KEY'])
result = client.upload_gem('pkg/myapp-1.2.3.gem')
# => {"repo":"gems-local","path":"myapp-1.2.3.gem","created":"2025-10-07T15:30:00.000Z"}
Sonatype Nexus Repository serves similar purposes with a focus on Java ecosystem integration. Nexus Repository Manager handles Maven, npm, Docker, and other formats. The platform includes security scanning capabilities that analyze artifacts for known vulnerabilities during upload.
Nexus provides repository health checks, identifying artifacts with security issues or license compliance problems. Organizations use these features to enforce policies - blocking deployment of artifacts with critical vulnerabilities or incompatible licenses.
GitHub Packages integrates artifact management directly into GitHub repositories. It supports Docker containers, npm packages, RubyGems, and Maven artifacts. GitHub Packages links artifacts to source repositories automatically, creating tight coupling between code and build outputs.
Authentication uses GitHub personal access tokens or GitHub Actions secrets. Publishing from GitHub Actions requires minimal configuration:
# .github/workflows/publish.yml integration
# Gemspec configuration for GitHub Packages
Gem::Specification.new do |spec|
spec.name = 'example-gem'
spec.version = '1.0.0'
spec.authors = ['Development Team']
spec.metadata['allowed_push_host'] = 'https://rubygems.pkg.github.com/organization'
end
AWS CodeArtifact provides managed artifact repositories in AWS infrastructure. It integrates with AWS IAM for access control and AWS CloudTrail for audit logging. CodeArtifact supports Maven, npm, Python, and NuGet repositories with cross-region replication.
The service handles repository infrastructure, backups, and scaling automatically. Pricing follows AWS patterns - charges based on storage volume and data transfer. Organizations already using AWS services find tight integration with CodePipeline and other AWS tools.
GitLab Package Registry includes artifact management in GitLab's integrated DevOps platform. The registry stores packages alongside source code, merge requests, and CI/CD pipelines. It supports Maven, npm, Composer, NuGet, and PyPI formats.
RubyGems.org serves as the public repository for Ruby gems but organizations often run private gem servers. Tools like gem server, geminabox, and gemfury provide private gem hosting:
# Publishing to private gem server using geminabox
require 'gemfury'
class PrivateGemPublisher
def initialize(push_token)
@client = Gemfury::Client.new(user_api_key: push_token)
end
def publish(gem_file)
response = @client.push_gem(File.new(gem_file))
{
name: response['name'],
version: response['version'],
published_at: response['created_at']
}
rescue Gemfury::NotFound
raise "Failed to publish gem"
end
def versions(gem_name)
@client.list_gem_versions(gem_name).map do |version|
{
version: version['version'],
created: version['created_at']
}
end
end
end
Docker Registry stores container images, a specialized form of artifact. Docker Hub provides public hosting, while Docker Registry and Harbor provide self-hosted alternatives. Container registries implement the OCI Distribution Specification for standardized image storage and retrieval.
Ruby Implementation
Ruby projects generate artifacts primarily as gems - packages containing Ruby code, dependencies, and metadata. The gem build command compiles a gemspec into a .gem file, which functions as the distributable artifact.
# myapp.gemspec - defines the artifact
Gem::Specification.new do |spec|
spec.name = 'myapp'
spec.version = '2.1.0'
spec.summary = 'Application service'
spec.description = 'Core business logic service'
spec.authors = ['Development Team']
spec.email = 'dev@example.com'
spec.files = Dir['lib/**/*.rb', 'bin/*', 'README.md']
spec.binaries = ['myapp-server']
spec.require_paths = ['lib']
# Runtime dependencies become artifact dependencies
spec.add_runtime_dependency 'sinatra', '~> 3.0'
spec.add_runtime_dependency 'sequel', '~> 5.0'
# Development dependencies not included in production artifact
spec.add_development_dependency 'rspec', '~> 3.12'
spec.add_development_dependency 'rubocop', '~> 1.50'
# Metadata for artifact discovery
spec.metadata = {
'source_code_uri' => 'https://github.com/example/myapp',
'changelog_uri' => 'https://github.com/example/myapp/CHANGELOG.md',
'bug_tracker_uri' => 'https://github.com/example/myapp/issues'
}
end
Building and publishing gems integrates with artifact repositories:
# Rakefile - automates artifact creation and publishing
require 'rubygems/package_task'
require 'rake/clean'
spec = Gem::Specification.load('myapp.gemspec')
Gem::PackageTask.new(spec) do |pkg|
pkg.need_tar = false
end
namespace :artifact do
desc 'Publish gem to private repository'
task :publish => :gem do
gem_file = "pkg/myapp-#{spec.version}.gem"
# Push to private repository
sh "gem push #{gem_file} --host https://gems.example.com"
# Record publication metadata
File.write('pkg/publish.json', JSON.pretty_generate({
artifact: spec.name,
version: spec.version,
published_at: Time.now.iso8601,
checksum: Digest::SHA256.file(gem_file).hexdigest
}))
end
desc 'Verify artifact integrity'
task :verify do
gem_file = "pkg/myapp-#{spec.version}.gem"
# Verify gem can be unpacked
Dir.mktmpdir do |tmpdir|
sh "gem unpack #{gem_file} --target=#{tmpdir}"
puts "Artifact verified successfully"
end
end
end
Rails applications don't typically distribute as gems but generate deployment artifacts through asset compilation and dependency bundling:
# lib/tasks/artifact.rake
namespace :artifact do
desc 'Prepare deployment artifact'
task :prepare => :environment do
# Precompile assets to create static artifact files
Rake::Task['assets:precompile'].invoke
# Bundle dependencies for deployment
sh 'bundle config set --local deployment true'
sh 'bundle package --all'
# Create artifact manifest
manifest = {
rails_version: Rails.version,
ruby_version: RUBY_VERSION,
asset_digest: asset_digest,
bundled_gems: bundled_gem_list,
created_at: Time.now.iso8601
}
File.write('artifact_manifest.json', JSON.pretty_generate(manifest))
end
def asset_digest
Rails.application.assets_manifest.files.transform_values { |attrs| attrs['integrity'] }
end
def bundled_gem_list
Bundler.load.specs.map do |spec|
{
name: spec.name,
version: spec.version.to_s,
source: spec.source.class.name
}
end
end
end
Managing gem dependencies involves configuring Bundler to fetch from private repositories:
# Gemfile - configure artifact sources
source 'https://rubygems.org'
# Private gem server for internal artifacts
source 'https://gems.example.com' do
gem 'internal-auth-lib', '~> 2.0'
gem 'internal-logging', '~> 1.5'
end
# Public gems
gem 'rails', '~> 7.0.0'
gem 'pg', '~> 1.4'
# Lock specific versions for reproducibility
gem 'redis', '5.0.6' # Exact version pin
group :development, :test do
gem 'rspec-rails', '~> 6.0'
end
Bundler configuration for artifact authentication:
# Bundle configuration stored in .bundle/config
# Credentials for private artifact repositories
# Configure programmatically
Bundler.settings.set_global('gems.example.com', ENV['GEM_SERVER_TOKEN'])
# Or via bundle config command:
# bundle config gems.example.com $GEM_SERVER_TOKEN
Docker containers represent another artifact format for Ruby applications:
# Dockerfile - defines container artifact
FROM ruby:3.2-slim
WORKDIR /app
# Copy dependency specifications first for layer caching
COPY Gemfile Gemfile.lock ./
# Install dependencies from artifact repositories
RUN bundle config set --local deployment true && \
bundle config set --local without development:test && \
bundle install
# Copy application code
COPY . .
# Precompile assets as part of artifact
RUN bundle exec rake assets:precompile
# Artifact metadata as labels
LABEL version="2.1.0" \
build.number="1847" \
build.timestamp="2025-10-07T15:30:00Z" \
source.repository="https://github.com/example/myapp" \
source.commit="a3b2c1d4e5f6"
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]
Practical Examples
Continuous Integration Pipeline demonstrates artifact creation and publication in an automated build:
# ci/build_pipeline.rb - orchestrates artifact lifecycle
require 'json'
require 'digest'
require 'net/http'
class BuildPipeline
attr_reader :version, :build_number
def initialize
@version = File.read('VERSION').strip
@build_number = ENV['CI_BUILD_NUMBER']
@artifact_repo = ENV['ARTIFACT_REPOSITORY_URL']
end
def execute
validate_environment
build_artifacts
run_tests
calculate_checksums
publish_artifacts
tag_release
end
private
def validate_environment
required_vars = %w[CI_BUILD_NUMBER ARTIFACT_REPOSITORY_URL REPOSITORY_TOKEN]
missing = required_vars.select { |var| ENV[var].nil? }
raise "Missing environment variables: #{missing.join(', ')}" if missing.any?
end
def build_artifacts
puts "Building version #{@version} (build #{@build_number})"
# Build gem artifact
sh 'gem build myapp.gemspec'
# Build Docker image artifact
sh "docker build -t myapp:#{@version}-#{@build_number} ."
sh "docker tag myapp:#{@version}-#{@build_number} myapp:latest"
end
def run_tests
sh 'bundle exec rspec'
end
def calculate_checksums
gem_file = "myapp-#{@version}.gem"
@checksums = {
sha256: Digest::SHA256.file(gem_file).hexdigest,
sha512: Digest::SHA512.file(gem_file).hexdigest
}
File.write("#{gem_file}.sha256", @checksums[:sha256])
File.write("#{gem_file}.sha512", @checksums[:sha512])
end
def publish_artifacts
gem_file = "myapp-#{@version}.gem"
# Publish gem to artifact repository
uri = URI("#{@artifact_repo}/gems/#{gem_file}")
request = Net::HTTP::Put.new(uri)
request['Authorization'] = "Bearer #{ENV['REPOSITORY_TOKEN']}"
request['X-Checksum-Sha256'] = @checksums[:sha256]
request.body = File.read(gem_file)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
raise "Failed to publish: #{response.body}" unless response.is_a?(Net::HTTPSuccess)
# Publish Docker image
sh "docker push myapp:#{@version}-#{@build_number}"
sh "docker push myapp:latest"
record_publication_metadata
end
def record_publication_metadata
metadata = {
version: @version,
build_number: @build_number,
checksums: @checksums,
artifacts: [
{
type: 'gem',
filename: "myapp-#{@version}.gem",
url: "#{@artifact_repo}/gems/myapp-#{@version}.gem"
},
{
type: 'docker',
tag: "myapp:#{@version}-#{@build_number}",
registry: ENV['DOCKER_REGISTRY']
}
],
built_at: Time.now.iso8601,
source_commit: ENV['GIT_COMMIT']
}
File.write('build_metadata.json', JSON.pretty_generate(metadata))
end
def tag_release
return unless release_build?
sh "git tag v#{@version}"
sh "git push origin v#{@version}"
end
def release_build?
!@version.include?('SNAPSHOT') && ENV['CI_BRANCH'] == 'main'
end
def sh(cmd)
puts "$ #{cmd}"
system(cmd) || raise("Command failed: #{cmd}")
end
end
Deployment Process retrieves artifacts and deploys to production:
# deploy/artifact_deployer.rb
class ArtifactDeployer
def initialize(environment)
@environment = environment
@config = load_config(environment)
end
def deploy(artifact_coordinates)
artifact = fetch_artifact(artifact_coordinates)
verify_artifact(artifact)
prepare_deployment_directory
extract_artifact(artifact)
update_dependencies
restart_services
verify_deployment
end
private
def fetch_artifact(coordinates)
# Coordinates: name:version or name:version:build_number
name, version, build_number = coordinates.split(':')
url = if build_number
"#{@config[:repository]}/gems/#{name}-#{version}.gem"
else
"#{@config[:repository]}/gems/#{name}-#{version}.gem"
end
uri = URI(url)
request = Net::HTTP::Get.new(uri)
request['Authorization'] = "Bearer #{@config[:token]}"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
raise "Artifact not found: #{coordinates}" unless response.is_a?(Net::HTTPSuccess)
{
content: response.body,
checksum: response['X-Checksum-Sha256'],
filename: "#{name}-#{version}.gem"
}
end
def verify_artifact(artifact)
computed = Digest::SHA256.hexdigest(artifact[:content])
if artifact[:checksum] && computed != artifact[:checksum]
raise "Checksum mismatch! Expected #{artifact[:checksum]}, got #{computed}"
end
# Write artifact to disk
File.binwrite("deploy/#{artifact[:filename]}", artifact[:content])
# Verify gem structure
Dir.mktmpdir do |tmpdir|
system("gem unpack deploy/#{artifact[:filename]} --target=#{tmpdir}")
raise "Invalid gem structure" unless $?.success?
end
end
def prepare_deployment_directory
FileUtils.mkdir_p('deploy/releases')
@release_path = "deploy/releases/#{Time.now.strftime('%Y%m%d%H%M%S')}"
FileUtils.mkdir_p(@release_path)
end
def extract_artifact(artifact)
system("gem unpack deploy/#{artifact[:filename]} --target=#{@release_path}")
end
def update_dependencies
Dir.chdir(@release_path) do
# Install dependencies from artifact repositories
system('bundle config set --local deployment true')
system('bundle install')
end
end
def load_config(environment)
JSON.parse(File.read("config/deploy/#{environment}.json"), symbolize_names: true)
end
end
Multi-Format Artifact Management handles different artifact types in a unified workflow:
class MultiFormatArtifactManager
def initialize(repository_url, credentials)
@repository_url = repository_url
@credentials = credentials
end
def publish_release(version)
artifacts = []
# Build and publish gem
gem_artifact = build_gem(version)
publish_gem(gem_artifact)
artifacts << gem_artifact
# Build and publish Docker image
docker_artifact = build_docker_image(version)
publish_docker_image(docker_artifact)
artifacts << docker_artifact
# Generate release notes
generate_release_manifest(version, artifacts)
end
def build_gem(version)
gem_file = "myapp-#{version}.gem"
system("gem build myapp.gemspec")
{
type: 'gem',
filename: gem_file,
version: version,
checksum: Digest::SHA256.file(gem_file).hexdigest,
size: File.size(gem_file)
}
end
def publish_gem(artifact)
uri = URI("#{@repository_url}/gems/#{artifact[:filename]}")
File.open(artifact[:filename], 'rb') do |file|
request = Net::HTTP::Put.new(uri)
request['Authorization'] = "Bearer #{@credentials[:token]}"
request['Content-Type'] = 'application/octet-stream'
request['X-Checksum-Sha256'] = artifact[:checksum]
request.body = file.read
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
raise "Publish failed: #{response.body}" unless response.is_a?(Net::HTTPSuccess)
end
artifact[:published_url] = uri.to_s
artifact[:published_at] = Time.now.iso8601
end
def build_docker_image(version)
image_tag = "myapp:#{version}"
system("docker build -t #{image_tag} .")
# Get image digest
digest = `docker inspect --format='{{index .RepoDigests 0}}' #{image_tag}`.strip
{
type: 'docker',
tag: image_tag,
digest: digest,
version: version
}
end
def generate_release_manifest(version, artifacts)
manifest = {
version: version,
released_at: Time.now.iso8601,
artifacts: artifacts,
git_commit: `git rev-parse HEAD`.strip,
git_tag: "v#{version}",
changelog: extract_changelog(version)
}
File.write("releases/#{version}.json", JSON.pretty_generate(manifest))
end
end
Common Patterns
Snapshot and Release Workflow separates development artifacts from production artifacts:
class SnapshotReleaseWorkflow
def initialize
@version = calculate_version
@is_release = release_branch?
end
def publish
artifact_version = @is_release ? @version : "#{@version}-SNAPSHOT"
repository = @is_release ? 'releases' : 'snapshots'
build_artifact(artifact_version)
publish_to_repository(repository, artifact_version)
if @is_release
tag_release
promote_to_production_repo
end
end
private
def calculate_version
# Read from VERSION file or git tags
File.read('VERSION').strip
end
def release_branch?
current_branch = `git rev-parse --abbrev-ref HEAD`.strip
current_branch == 'main' || current_branch.start_with?('release/')
end
def publish_to_repository(repository, version)
# Snapshots allow overwrite, releases do not
allow_overwrite = repository == 'snapshots'
# Implementation details...
end
end
Dependency Lock Pattern ensures reproducible builds by locking artifact versions:
# Gemfile.lock serves this purpose for Ruby
# Example of custom lock file for non-gem artifacts
class DependencyLock
def initialize(lockfile_path = 'artifact.lock')
@lockfile_path = lockfile_path
@dependencies = load_lock_file
end
def resolve_dependencies(requirements)
resolved = {}
requirements.each do |name, constraint|
locked_version = @dependencies[name]
if locked_version && satisfies_constraint?(locked_version, constraint)
resolved[name] = locked_version
else
# Resolve new version and update lock
resolved[name] = resolve_version(name, constraint)
@dependencies[name] = resolved[name]
end
end
save_lock_file
resolved
end
private
def load_lock_file
return {} unless File.exist?(@lockfile_path)
JSON.parse(File.read(@lockfile_path))
end
def save_lock_file
File.write(@lockfile_path, JSON.pretty_generate({
locked_at: Time.now.iso8601,
dependencies: @dependencies
}))
end
end
Promotion Pipeline Pattern moves artifacts through quality gates:
class ArtifactPromotionPipeline
STAGES = ['snapshot', 'testing', 'staging', 'production'].freeze
def initialize(artifact_id)
@artifact = fetch_artifact_metadata(artifact_id)
@current_stage = @artifact[:stage] || 'snapshot'
end
def promote
next_stage = STAGES[STAGES.index(@current_stage) + 1]
raise "Already at final stage" if next_stage.nil?
validate_promotion_criteria(next_stage)
copy_artifact_to_stage(next_stage)
update_metadata(next_stage)
notify_stakeholders(next_stage)
next_stage
end
private
def validate_promotion_criteria(target_stage)
case target_stage
when 'testing'
raise "Build must pass" unless @artifact[:build_status] == 'passed'
when 'staging'
raise "Tests must pass" unless @artifact[:test_status] == 'passed'
raise "Security scan required" unless @artifact[:security_scan_passed]
when 'production'
raise "Approval required" unless @artifact[:approved_by]
raise "Staging validation required" unless @artifact[:staging_validated]
end
end
def copy_artifact_to_stage(stage)
source_repo = repository_for_stage(@current_stage)
target_repo = repository_for_stage(stage)
# Copy artifact between repositories
artifact_path = @artifact[:path]
# Implementation uses repository APIs to copy
# Some systems support server-side copy without download/upload
end
end
Artifact Mirroring Pattern maintains local copies of external dependencies:
class ArtifactMirror
def initialize(external_source, local_repository)
@external = external_source
@local = local_repository
@cache_ttl = 3600 # 1 hour
end
def fetch(artifact_coordinates)
# Check local cache first
if cached = @local.fetch(artifact_coordinates)
return cached if cache_valid?(cached)
end
# Fetch from external source
external_artifact = @external.fetch(artifact_coordinates)
# Store in local repository
@local.store(artifact_coordinates, external_artifact, {
cached_at: Time.now,
source: @external.name
})
external_artifact
end
def preload_dependencies(manifest)
# Bulk mirror dependencies before they're needed
manifest.each do |dependency|
fetch(dependency) unless @local.exists?(dependency)
end
end
private
def cache_valid?(cached_artifact)
cached_time = cached_artifact[:cached_at]
Time.now - Time.parse(cached_time) < @cache_ttl
end
end
Security Implications
Access Control prevents unauthorized artifact publication and consumption. Production artifact repositories require authentication for all operations. Read access might be open within the organization but write access restricts to automated build systems and designated release engineers.
Role-based access control maps users to permissions. Developers receive read access to all repositories and write access to snapshot repositories. CI/CD systems use service accounts with write permissions to specific repositories. Repository administrators control permission assignments.
class ArtifactAccessControl
def initialize(repository, auth_provider)
@repository = repository
@auth = auth_provider
end
def authorize_read(user, artifact)
permissions = @auth.get_permissions(user)
if @repository == 'public'
return true
elsif @repository == 'snapshots'
permissions.include?(:read_snapshots) || permissions.include?(:admin)
elsif @repository == 'releases'
permissions.include?(:read_releases) || permissions.include?(:admin)
else
permissions.include?(:admin)
end
end
def authorize_write(user, artifact)
permissions = @auth.get_permissions(user)
# Only CI systems and admins can write to releases
if @repository == 'releases'
return permissions.include?(:ci_publisher) || permissions.include?(:admin)
end
# Developers can write to snapshots
if @repository == 'snapshots'
return permissions.include?(:developer) || permissions.include?(:admin)
end
false
end
end
Checksum Verification protects against corrupted or tampered artifacts. Every artifact download must verify checksums match before execution. Clients reject artifacts with mismatched checksums and report the discrepancy.
def download_and_verify(artifact_url, expected_checksum)
response = Net::HTTP.get_response(URI(artifact_url))
raise "Download failed" unless response.is_a?(Net::HTTPSuccess)
content = response.body
actual_checksum = Digest::SHA256.hexdigest(content)
if actual_checksum != expected_checksum
raise SecurityError, "Checksum mismatch for #{artifact_url}. " \
"Expected: #{expected_checksum}, Got: #{actual_checksum}"
end
content
end
Signature Verification proves artifact authenticity through cryptographic signatures. Publishers sign artifacts with private keys, and consumers verify signatures using public keys. This prevents artifact substitution attacks.
Organizations maintain key infrastructure for artifact signing. Build systems access signing keys through secure credential management. Public keys distribute to developers and deployment systems for verification.
Vulnerability Scanning identifies security issues in artifacts before deployment. Automated scanners analyze artifacts for known vulnerabilities in dependencies. Critical vulnerabilities block artifact promotion to production.
Integration with vulnerability databases provides up-to-date threat information. Scanners check dependencies against CVE databases and report findings. Security teams review scan results and approve exceptions when necessary.
Artifact Immutability prevents tampering after publication. Once an artifact publishes to a release repository, the system rejects any attempts to modify it. This immutability provides assurance that artifacts haven't changed since their security scan and approval.
Attempting to republish to an existing version fails:
def publish_artifact(artifact, version)
coordinates = "#{artifact.name}:#{version}"
if repository.exists?(coordinates) && repository.type == 'releases'
raise ImmutabilityError, "Artifact #{coordinates} already exists and cannot be modified"
end
repository.store(coordinates, artifact)
end
Credential Management protects artifact repository access tokens. Build systems retrieve credentials from secret management services rather than storing them in source control. Credentials rotate regularly and have limited scopes.
Token scopes limit damage from credential compromise. A token with only read access to snapshots cannot publish to production repositories. Time-limited tokens expire automatically, requiring regular renewal.
Reference
Artifact Types and Formats
| Type | Extension | Description | Primary Use |
|---|---|---|---|
| Ruby Gem | .gem | Packaged Ruby library or application | Ruby dependencies |
| Maven JAR | .jar | Java archive | Java libraries and applications |
| Maven WAR | .war | Web application archive | Java web applications |
| Docker Image | N/A | Container image | Application deployment |
| npm Package | .tgz | Node.js package archive | JavaScript dependencies |
| Python Wheel | .whl | Python distribution format | Python dependencies |
| Tarball | .tar.gz | Compressed archive | Source distributions |
| Debian Package | .deb | Debian package format | Linux software distribution |
Repository Types
| Repository Type | Mutability | Purpose | Typical Retention |
|---|---|---|---|
| Snapshot | Mutable | Development builds | 30-90 days |
| Release | Immutable | Production artifacts | Indefinite or 1+ years |
| Remote | Cached | Proxy external sources | Based on usage |
| Virtual | N/A | Aggregate repositories | N/A |
Common Artifact Coordinates
| Ecosystem | Coordinate Format | Example |
|---|---|---|
| Maven | groupId:artifactId:version | com.example:service:2.1.0 |
| Ruby Gems | name-version | rails-7.0.4 |
| npm | @scope/name@version | @company/package@1.2.3 |
| Docker | registry/repository:tag | docker.io/myapp:2.1.0 |
| PyPI | name==version | requests==2.28.0 |
Metadata Fields
| Field | Type | Description | Required |
|---|---|---|---|
| name | String | Artifact identifier | Yes |
| version | String | Version number | Yes |
| checksum | String | SHA-256 or SHA-512 hash | Recommended |
| size | Integer | File size in bytes | No |
| published_at | Timestamp | Publication time | No |
| build_number | Integer | CI build identifier | No |
| source_commit | String | Git commit SHA | Recommended |
| dependencies | Array | Required artifacts | Recommended |
Security Controls
| Control | Purpose | Implementation |
|---|---|---|
| Authentication | Verify user identity | API tokens, OAuth |
| Authorization | Control access permissions | RBAC, ACLs |
| Checksum Verification | Detect corruption and tampering | SHA-256, SHA-512 |
| Signature Verification | Prove authenticity | GPG, code signing |
| Vulnerability Scanning | Identify known issues | CVE database lookup |
| Access Logging | Audit trail | Centralized logging |
| Encryption in Transit | Protect network transfer | TLS/HTTPS |
| Encryption at Rest | Protect stored artifacts | Filesystem encryption |
Bundler Configuration Commands
| Command | Purpose |
|---|---|
| bundle config set --local deployment true | Enable deployment mode |
| bundle config set --local without development:test | Skip development dependencies |
| bundle package --all | Cache all dependencies locally |
| bundle config gems.example.com TOKEN | Set credentials for private repository |
| bundle install --frozen | Install exact versions from lockfile |
| bundle outdated | Check for newer versions |
Repository API Operations
| Operation | HTTP Method | Purpose |
|---|---|---|
| Publish Artifact | PUT | Upload artifact to repository |
| Fetch Artifact | GET | Download artifact by coordinates |
| Delete Artifact | DELETE | Remove artifact from repository |
| Search Artifacts | GET | Query available artifacts |
| Get Metadata | GET | Retrieve artifact metadata |
| List Versions | GET | Get all versions of an artifact |
Checksum Algorithms
| Algorithm | Output Length | Security Level | Use Case |
|---|---|---|---|
| MD5 | 128 bits | Weak | Legacy compatibility only |
| SHA-1 | 160 bits | Weak | Deprecated |
| SHA-256 | 256 bits | Strong | Standard integrity verification |
| SHA-512 | 512 bits | Strong | High-security requirements |