Migrating from JFrog Artifactory to CloudRepo represents one of the most impactful cost optimizations your organization can make. Teams trade consumption-based billing for flat, predictable pricing while escaping JFrog’s complex licensing model and surprise egress fees. This comprehensive guide provides everything you need for a successful migration.
Why Teams Are Escaping JFrog Artifactory
Cost savings are only part of it. Teams move to escape a pricing model that punishes growth and creates budget uncertainty.
The Hidden Costs of JFrog Artifactory
JFrog’s pricing model includes multiple cost vectors that compound quickly:
- A published minimum annual commitment ($15,000+ on JFrog’s list pricing) before you store a single artifact
- Egress fees that can double your bill - charged per GB downloaded
- User-based pricing that increases with team growth
- Storage overage charges when you exceed limits
- Mandatory support contracts at 20% of license cost
- Infrastructure costs for self-hosted deployments
CloudRepo’s Transparent Pricing Model
CloudRepo eliminates pricing complexity with a simple, predictable model:
- Storage-based pricing only - no per-user fees
- No egress fees (with reasonable soft limits per plan)
- No minimum commitments - pay for what you use
- Support included in all plans
- Transparent calculator - know your costs upfront
Cost Analysis: Calculate Your Savings
CloudRepo’s Business plan is a flat $799/month with no egress metering, no per-user fees, and support included. JFrog Artifactory prices on consumption instead: a base license plus per-GB egress and storage overages that climb as your CI/CD pipelines grow.
Because JFrog’s total depends on your storage and transfer profile, there’s no single “typical” bill. The more you build, the more you pay. Model your own usage against JFrog’s published rates with our pricing calculator to see your specific savings.
Pre-Migration Assessment
Before beginning your migration, conduct a thorough assessment of your Artifactory environment.
1. Audit Current Artifactory Usage
# Get repository statisticscurl -u admin:password \ "https://artifactory.example.com/artifactory/api/storageinfo" \ | jq '.repositoriesSummaryList[]'
# List all repositories
curl -u admin:password \ "https://artifactory.example.com/artifactory/api/repositories" \ | jq '.[].key'
# Check user count and permissions
curl -u admin:password \ "https://artifactory.example.com/artifactory/api/security/users" \ | jq '. | length'
# Analyze bandwidth usage (requires log analysis)
grep "Download" /var/log/artifactory/access.log | \ awk '{sum+=$10} END {print sum/1024/1024/1024 " GB"}'2. Document Repository Structure
Create a comprehensive inventory of your repositories:
repositories: maven: - name: libs-release type: local package_type: maven size: 850GB artifacts: 125000
- name: libs-snapshot type: local package_type: maven size: 420GB artifacts: 89000 cleanup_policy: 30_days
- name: maven-central type: remote url: https://repo1.maven.org/maven2 cache_size: 200GB
docker: - name: docker-localtype: localpackage_type: dockersize: 1.2TBimages: 3400
npm: - name: npm-localtype: localpackage_type: npmsize: 180GBpackages: 450003. Export Critical Configurations
# Export repository configurationsfor repo in $(curl -s -u admin:password \ "https://artifactory.example.com/artifactory/api/repositories" | \ jq -r '.[].key'); do
curl -u admin:password \ "https://artifactory.example.com/artifactory/api/repositories/$repo" \ > "configs/$repo.json"done
# Export users and permissions
curl -u admin:password \ "https://artifactory.example.com/artifactory/api/security/users" \
> users.json
curl -u admin:password \ "https://artifactory.example.com/artifactory/api/security/permissions" \
> permissions.json
# Export virtual repository configurations
curl -u admin:password \ "https://artifactory.example.com/artifactory/api/repositories?type=virtual" \
> virtual-repos.json4. Analyze Integration Points
Document all systems integrated with Artifactory:
- CI/CD Pipelines: Jenkins, GitHub Actions, GitLab CI
- Build Tools: Maven, Gradle, npm, Docker
- Deployment Systems: Kubernetes, Terraform, Ansible
- Monitoring: Datadog, New Relic, Prometheus
- Security Scanners: Snyk, SonarQube, Twistlock
Step-by-Step Migration Process
Step 1: Create CloudRepo Account and Initial Setup
- Sign up for CloudRepo at https://cloudrepo.io/signup
- Choose appropriate plan based on your storage needs
- Configure organization settings
# Set CloudRepo credentials as environment variablesexport CLOUDREPO_USER="your-username"export CLOUDREPO_API_TOKEN="YOUR_API_TOKEN"export CLOUDREPO_URL="https://your-org.cloudrepo.io"
# Verify connection
curl -u $CLOUDREPO_USER:$CLOUDREPO_API_TOKEN \ "$CLOUDREPO_URL/api/v1/account/info"Step 2: Map Artifactory Repositories to CloudRepo
Create equivalent repositories in CloudRepo for each Artifactory repository:
Repository Type Mapping
| Artifactory Type | CloudRepo Equivalent | Notes |
|---|---|---|
| Local Repository | Repository | Direct mapping |
| Remote Repository | Proxy Repository | Caching proxy for external Maven/Python repos |
| Virtual Repository | Repository Group | Aggregates multiple repositories |
| Generic Repository | Raw Repository | For arbitrary file storage |
import requestsimport json
# Read Artifactory repository configurations
with open('repository-inventory.yaml', 'r') as f:inventory = yaml.safe_load(f)
cloudrepo_api = "https://your-org.cloudrepo.io/api/v1"auth = ("username", "password")
for repo_type, repos in inventory['repositories'].items():for repo in repos: # Create CloudRepo repositorypayload = {"name": repo['name'],"type": repo_type,"description": f"Migrated from Artifactory {repo['name']}"}
response = requests.post( f"{cloudrepo_api}/repositories", json=payload, auth=auth )
if response.status_code == 201: print(f"Created repository: {repo['name']}") else: print(f"Failed to create {repo['name']}: {response.text}")Step 3: Export Artifacts from Artifactory
Use JFrog CLI for efficient bulk export:
#!/bin/bash# Install JFrog CLI if not present
if ! command -v jf &> /dev/null; thencurl -fL https://getcli.jfrog.io/v2 | shchmod +x jfsudo mv jf /usr/local/bin/fi
# Configure JFrog CLI
jf config add artifactory-server \ --artifactory-url="https://artifactory.example.com/artifactory" \ --user="admin" \ --password="password"
# Export repositories
REPOS=("libs-release""libs-snapshot""docker-local""npm-local")
for repo in "${REPOS[@]}"; doecho "Exporting $repo..."
# Create export directory
mkdir -p exports/$repo
# Download all artifacts from repository
jf rt download \ "$repo/*" \ "exports/$repo/" \ --flat=false \ --threads=10 \ --retry=3
# Generate manifest
find exports/$repo -type f | \ sed "s|exports/$repo/||" > exports/$repo.manifest
echo "Exported $(wc -l < exports/$repo.manifest) artifacts from $repo"doneStep 4: Import Artifacts to CloudRepo
Efficiently upload artifacts to CloudRepo:
#!/usr/bin/env python3import osimport requestsfrom concurrent.futures import ThreadPoolExecutor, as_completedfrom pathlib import Pathimport hashlib
CLOUDREPO_URL = "https://your-org.cloudrepo.io"AUTH = ("username", "password")MAX_WORKERS = 10
def calculate_checksum(file_path):"""Calculate SHA256 checksum of file."""sha256_hash = hashlib.sha256()with open(file_path, "rb") as f:for byte_block in iter(lambda: f.read(4096), b""):sha256_hash.update(byte_block)return sha256_hash.hexdigest()
def upload_artifact(local_path, repo_name, artifact_path):"""Upload single artifact to CloudRepo."""url = f"{CLOUDREPO_URL}/repository/{repo_name}/{artifact_path}"
# Calculate checksum checksum = calculate_checksum(local_path)
# Upload file with open(local_path, 'rb') as f: headers = { 'X-Checksum-SHA256': checksum } response = requests.put( url, data=f, auth=AUTH, headers=headers )
if response.status_code == 201: return f"✓ {artifact_path}" else: return f"✗ {artifact_path}: {response.status_code}"
def migrate_repository(repo_name, export_dir):"""Migrate entire repository to CloudRepo."""export_path = Path(export_dir)artifacts = list(export_path.rglob('\*'))artifacts = [a for a in artifacts if a.is_file()]
print(f"Migrating {len(artifacts)} artifacts from {repo_name}")
results = [] with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor: futures = {}
for artifact_file in artifacts: relative_path = artifact_file.relative_to(export_path) future = executor.submit( upload_artifact, str(artifact_file), repo_name, str(relative_path) ) futures[future] = str(relative_path)
for future in as_completed(futures): result = future.result() print(result) results.append(result)
successful = len([r for r in results if r.startswith('✓')]) print(f"Migration complete: {successful}/{len(artifacts)} artifacts uploaded")
# Migrate all repositories
repositories = [("libs-release", "exports/libs-release"),("libs-snapshot", "exports/libs-snapshot"),("docker-local", "exports/docker-local"),("npm-local", "exports/npm-local"),]
for repo_name, export_dir in repositories:migrate_repository(repo_name, export_dir)Step 5: Update Build Configurations
Maven Configuration
Update settings.xml to point to CloudRepo. For a refresher on how Maven repositories work, including settings.xml configuration, see our comprehensive guide.
<settings> <servers> <server> <id>cloudrepo-releases</id> <username>${env.CLOUDREPO_USER}</username> <password>${env.CLOUDREPO_API_TOKEN}</password> </server> <server> <id>cloudrepo-snapshots</id> <username>${env.CLOUDREPO_USER}</username> <password>${env.CLOUDREPO_API_TOKEN}</password> </server> </servers>
<mirrors> <mirror> <id>cloudrepo-central</id> <mirrorOf>central</mirrorOf> <url>https://your-org.mycloudrepo.io/repositories/maven-central/</url> </mirror></mirrors>
<profiles> <profile> <id>cloudrepo</id> <activation> <activeByDefault>true</activeByDefault> </activation> <repositories> <repository> <id>cloudrepo-releases</id> <url>https://your-org.mycloudrepo.io/repositories/libs-release/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>cloudrepo-snapshots</id> <url>https://your-org.mycloudrepo.io/repositories/libs-snapshot/</url> <releases> <enabled>false</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> </profile> </profiles></settings>Gradle Configuration
Update gradle.properties and build scripts:
cloudrepoUrl=https://your-org.cloudrepo.iocloudrepoUsername=your-usernamecloudrepoPassword=YOUR_API_TOKEN
// build.gradlerepositories {maven {url "${cloudrepoUrl}/repository/libs-release/" credentials { username cloudrepoUsername password cloudrepoPassword } } maven { url "${cloudrepoUrl}/repository/libs-snapshot/"credentials {username cloudrepoUsernamepassword cloudrepoPassword}}}
publishing {repositories {maven {name = 'cloudrepo'url = uri("${cloudrepoUrl}/repository/libs-${version.endsWith('SNAPSHOT') ? 'snapshot' : 'release'}/")credentials {username = cloudrepoUsernamepassword = cloudrepoPassword}}}}Step 6: Migrate Virtual Repositories
Create Repository Groups in CloudRepo to replace Artifactory Virtual Repositories:
import jsonimport requests
# Load virtual repository configurations
with open('virtual-repos.json', 'r') as f:virtual_repos = json.load(f)
cloudrepo_api = "https://your-org.cloudrepo.io/api/v1"auth = ("username", "password")
for vrepo in virtual_repos: # Create repository group in CloudRepogroup_config = {"name": vrepo['key'],"type": "group","repositories": vrepo['repositories'],"description": f"Migrated virtual repository from Artifactory"}
response = requests.post( f"{cloudrepo_api}/repository-groups", json=group_config, auth=auth )
if response.status_code == 201: print(f"Created repository group: {vrepo['key']}") print(f" Members: {', '.join(vrepo['repositories'])}")Step 7: Configure Authentication and Permissions
Migrate users and set up appropriate permissions:
import jsonimport requestsimport randomimport string
def generate_password():"""Generate secure random password."""return ''.join(random.choices(string.ascii_letters + string.digits + string.punctuation,k=16))
# Load Artifactory users
with open('users.json', 'r') as f:artifactory_users = json.load(f)
cloudrepo_api = "https://your-org.cloudrepo.io/api/v1"auth = ("admin", "admin_password")
user_mappings = []
for user in artifactory_users: # Skip system usersif user['name'] in ['anonymous', '_internal']:continue
# Generate new password for CloudRepo new_password = generate_password()
# Create user in CloudRepo user_data = { "username": user['name'], "email": user['email'], "password": new_password, "roles": ["developer"] # Map Artifactory roles to CloudRepo roles }
response = requests.post( f"{cloudrepo_api}/users", json=user_data, auth=auth )
if response.status_code == 201: user_mappings.append({ "username": user['name'], "email": user['email'], "password": new_password }) print(f"Created user: {user['name']}")
# Save user mappings for distribution
with open('cloudrepo_users.json', 'w') as f:json.dump(user_mappings, f, indent=2)
print(f"\nMigrated {len(user_mappings)} users")print("User credentials saved to cloudrepo_users.json")Step 8: Update CI/CD Pipelines
Jenkins Migration
Update Jenkins to use CloudRepo:
// Jenkinsfilepipeline { agent any
environment { CLOUDREPO_CREDS = credentials('cloudrepo-credentials') CLOUDREPO_URL = 'https://your-org.cloudrepo.io' }
stages { stage('Build') { steps { sh ''' # Configure Maven settings echo "<settings> <servers> <server> <id>cloudrepo</id> <username>${CLOUDREPO_CREDS_USR}</username> <password>${CLOUDREPO_CREDS_PSW}</password> </server> </servers> </settings>" > settings.xml
# Build with CloudRepo repositories mvn -s settings.xml clean package ''' } }
stage('Deploy') { steps { sh ''' mvn -s settings.xml deploy \ -DaltDeploymentRepository=cloudrepo::default::${CLOUDREPO_URL}/repository/libs-release/ ''' } } }
}GitHub Actions Migration
name: Build and Deploy
on:push:branches: [main]
jobs:build:runs-on: ubuntu-latest
steps: - uses: actions/checkout@v3
- name: Setup Java uses: actions/setup-java@v3 with: java-version: '11' distribution: 'adopt'
- name: Configure Maven for CloudRepo run: | mkdir -p ~/.m2 cat > ~/.m2/settings.xml <<EOF <settings> <servers> <server> <id>cloudrepo</id> <username>${{ secrets.CLOUDREPO_USERNAME }}</username> <password>${{ secrets.CLOUDREPO_API_TOKEN }}</password> </server> </servers> </settings> EOF
- name: Build and Deploy run: | mvn clean deploy \ -DaltDeploymentRepository=cloudrepo::default::https://your-org.mycloudrepo.io/repositories/libs-release/ env: CLOUDREPO_USERNAME: ${{ secrets.CLOUDREPO_USERNAME }} CLOUDREPO_API_TOKEN: ${{ secrets.CLOUDREPO_API_TOKEN }}GitLab CI Migration
variables: CLOUDREPO_URL: "https://your-org.cloudrepo.io" MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"
cache:paths: - .m2/repository/
before_script:
- | cat > ~/.m2/settings.xml <<EOF <settings> <servers> <server> <id>cloudrepo</id> <username>${CLOUDREPO_USERNAME}</username> <password>${CLOUDREPO_API_TOKEN}</password> </server> </servers> </settings> EOF
build:stage: buildscript: - mvn clean compile
test:stage: testscript: - mvn test
deploy:stage: deployscript: - |mvn deploy \ -DaltDeploymentRepository=cloudrepo::default::${CLOUDREPO_URL}/repository/libs-release/only: - mainStep 9: Test and Validate
Comprehensive testing checklist:
#!/bin/bashecho "CloudRepo Migration Validation"echo "=============================="
# Test authentication
echo -n "Testing authentication... "if curl -s -u $CLOUDREPO_USER:$CLOUDREPO_API_TOKEN \ "$CLOUDREPO_URL/api/v1/account/info" > /dev/null; thenecho "✓"elseecho "✗"exit 1fi
# Test artifact download
echo -n "Testing artifact download... "if curl -s -u $CLOUDREPO_USER:$CLOUDREPO_API_TOKEN \ "$CLOUDREPO_URL/repository/libs-release/com/example/app/1.0/app-1.0.jar" \ -o /tmp/test-artifact.jar; thenecho "✓"elseecho "✗"fi
# Test artifact upload
echo -n "Testing artifact upload... "echo "test" > /tmp/test-file.txtif curl -s -u $CLOUDREPO_USER:$CLOUDREPO_API_TOKEN \ -X PUT \ -T /tmp/test-file.txt \ "$CLOUDREPO_URL/repository/libs-release/test/migration/test.txt"; thenecho "✓"elseecho "✗"fi
# Test build tool integration
echo -n "Testing Maven build... "cd sample-projectif mvn clean compile > /dev/null 2>&1; thenecho "✓"elseecho "✗"fi
# Compare artifact counts
echo -e "\nArtifact Count Comparison:"echo "-------------------------"for repo in libs-release libs-snapshot npm-local; doartifactory_count=$(grep -c "$repo" exports/$repo.manifest 2>/dev/null || echo 0) cloudrepo_count=$(curl -s -u $CLOUDREPO_USER:$CLOUDREPO_API_TOKEN \ "$CLOUDREPO_URL/api/v1/repositories/$repo/statistics" | \ jq '.artifactCount' 2>/dev/null || echo 0)
if [ "$artifactory_count" -eq "$cloudrepo_count" ]; then status="✓" else status="✗" fi
printf "%-20s Artifactory: %6d CloudRepo: %6d %s\n" \ "$repo" "$artifactory_count" "$cloudrepo_count" "$status"
done
echo -e "\nValidation complete!"Step 10: Decommission Artifactory
Once validation is complete, safely decommission Artifactory:
#!/bin/bash# Create final backup
echo "Creating final Artifactory backup..."curl -X POST -u admin:password \ "https://artifactory.example.com/artifactory/api/system/backup" \ -H "Content-Type: application/json" \ -d '{"key": "final-backup-$(date +%Y%m%d)","includeBuilds": true,"includeConfig": true}'
# Export audit logs
echo "Exporting audit logs..."tar -czf artifactory-logs-$(date +%Y%m%d).tar.gz \ /var/log/artifactory/
# Update DNS/Load Balancer (if using CNAME)
echo "Update DNS to point to CloudRepo:"echo " artifactory.example.com → your-org.cloudrepo.io"
# Notify teams
echo "Sending migration completion notification..."curl -X POST https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK \ -H 'Content-Type: application/json' \ -d '{"text": "Artifactory migration complete! New repository URL: https://your-org.cloudrepo.io","attachments": [{"color": "good","fields": [{"title": "Status", "value": "Migration complete", "short": true},{"title": "New Registry", "value": "your-org.cloudrepo.io", "short": true}]}]}'
# Schedule Artifactory shutdown
echo "Artifactory will be shut down in 30 days"echo "Final backup location: /backups/artifactory/final-backup-$(date +%Y%m%d)"Repository Type Migration Guide
Docker Registry Migration
CloudRepo serves the Docker Registry HTTP API V2, so migrating images is a re-tag and a push.
Note the shape difference: docker login takes the host on its own, while the image reference
carries a literal repositories/ segment.
docker login yourcompany.mycloudrepo.io
docker pull yourcompany.jfrog.io/docker-local/myimage:1.0.0docker tag yourcompany.jfrog.io/docker-local/myimage:1.0.0 \ yourcompany.mycloudrepo.io/repositories/docker-local/myimage:1.0.0docker push yourcompany.mycloudrepo.io/repositories/docker-local/myimage:1.0.0Authenticate with a Generic repository token from the admin portal rather than a portal password. An organization owner’s password is refused at the registry. Helm charts move the same way, since a CloudRepo Docker repository is an OCI registry.
NPM/PyPI Repository Migration
#!/bin/bash# Configure npm for CloudRepo
npm config set registry https://your-org.mycloudrepo.io/repositories/npm-group/npm config set always-auth truenpm config set _auth $(echo -n "$CLOUDREPO_USER:$CLOUDREPO_API_TOKEN" | base64)
# Export NPM packages from Artifactory
cd exports/npm-local
for package in $(find . -name "*.tgz"); doecho "Publishing $package to CloudRepo"npm publish $package --registry https://your-org.mycloudrepo.io/repositories/npm-local/done
# Python/PyPI migration
pip config set global.index-url https://$CLOUDREPO_USER:$CLOUDREPO_API_TOKEN@your-org.mycloudrepo.io/repositories/pypi-group/simple
# Upload Python packages
for package in $(find exports/pypi-local -name "*.whl" -o -name "*.tar.gz"); dotwine upload \ --repository-url https://your-org.mycloudrepo.io/repositories/pypi-local/ \ -u $CLOUDREPO_USER \ -p $CLOUDREPO_API_TOKEN \ $packagedoneFeature Comparison
Understanding what changes between Artifactory and CloudRepo:
What You’ll Gain
| Feature | CloudRepo Advantage |
|---|---|
| Cost | Flat, all-inclusive pricing with no consumption or egress fees |
| Pricing Model | Simple, transparent, no hidden fees |
| Egress Fees | None (reasonable soft limits) |
| Setup Time | 5 minutes vs days/weeks |
| Maintenance | Zero - fully managed |
| Support | Included in all plans |
| Performance | Managed cloud infrastructure |
| Availability | 99.9%+ historical uptime |
| Security | AES-256 encryption at rest, TLS 1.2+ in transit |
Feature Parity
| Feature | Artifactory | CloudRepo | Notes |
|---|---|---|---|
| Maven/Gradle | ✓ | ✓ | Full support |
| NPM | ✓ | ✓ | Full support |
| Docker | ✓ | ✓ | Registry HTTP API V2 |
| PyPI | ✓ | ✓ | Full support |
| NuGet | ✓ | ✗ | Not currently planned |
| REST API | ✓ | ✓ | Different endpoints |
| Virtual Repos | ✓ | ✓ | Called Repository Groups |
| Access Tokens | ✓ | ✓ | Full support |
| SAML/SSO | ✓ | Planned | On our roadmap |
| Webhooks | ✓ | ✓ | Full support |
| Replication | ✓ | ✗ | Use CDN for global performance |
| Xray Integration | ✓ | ✗ | Use alternative scanners |
Workarounds for Missing Features
JFrog Xray Alternative: Integrate with Snyk, Sonartype Nexus IQ, or GitHub Security Scanning
# Example: Snyk integration in CI/CD - name: Security Scan run: | snyk test--all-projects snyk monitor --all-projectsAdvanced Replication: Use CloudRepo’s API for custom replication:
import requestsimport scheduleimport time
def replicate_artifacts():"""Replicate artifacts between CloudRepo instances."""source_api = "https://primary.cloudrepo.io/api/v1"target_api = "https://backup.cloudrepo.io/api/v1"
# Get list of recent artifacts response = requests.get( f"{source_api}/repositories/libs-release/artifacts", params={"since": "24h"}, auth=("user", "pass") )
for artifact in response.json(): # Download from source artifact_data = requests.get( artifact['downloadUrl'], auth=("user", "pass") )
# Upload to target requests.put( f"{target_api}/repositories/libs-release/{artifact['path']}", data=artifact_data.content, auth=("user", "pass") )
# Schedule replication every hour
schedule.every().hour.do(replicate_artifacts)
while True:schedule.run_pending()time.sleep(60)Common Migration Challenges and Solutions
Challenge 1: Maven Snapshots with Timestamps
Problem: Artifactory snapshot timestamps differ from CloudRepo format
Solution: Update Maven configuration to handle both formats:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> <version>3.0.0</version> <configuration> <uniqueVersion>false</uniqueVersion> </configuration> </plugin> </plugins></build>Challenge 2: Permission Model Differences
Problem: Artifactory’s fine-grained permissions don’t map 1:1 to CloudRepo
Solution: Map Artifactory permissions to CloudRepo’s simpler team-based model:
permission_mapping = { "artifactory-admin": "cloudrepo-admin", "artifactory-deployer": "cloudrepo-developer", "artifactory-reader": "cloudrepo-reader", "artifactory-anonymous": None # No anonymous access in CloudRepo}
def map_user_permissions(artifactory_user):"""Map Artifactory permissions to CloudRepo roles."""cloudrepo_roles = []
for perm in artifactory_user['permissions']: if perm['repository'] == '*': cloudrepo_roles.append('admin') elif 'write' in perm['actions']: cloudrepo_roles.append('developer') elif 'read' in perm['actions']: cloudrepo_roles.append('reader')
return list(set(cloudrepo_roles)) # Remove duplicatesChallenge 3: Build Cache Dependencies
Problem: Builds fail due to missing cached dependencies
Solution: Pre-populate CloudRepo cache:
#!/bin/bash# Force download of all dependencies
mvn dependency:go-offline
# For Gradle
gradle build --refresh-dependencies
# For NPM
npm install --force
# For Python
pip download -r requirements.txt -d /tmp/pip-cachefor package in /tmp/pip-cache/*.whl; dotwine upload $packagedonePost-Migration Optimization
Performance Tuning
Optimize CloudRepo for maximum performance:
optimizations: caching: - Enable aggressive caching for release artifacts - Set 30-day cache for snapshots - Use standard CloudRepo endpoints for read-heavy repositories
networking: - Configure region-specific endpoints - Enable HTTP/2 for better multiplexing - Use connection pooling in CI/CD
artifacts: - Enable compression for text artifacts - Use shallow cloning for large repositories - Implement artifact cleanup policiesCost Monitoring
Track and optimize CloudRepo costs:
import requestsfrom datetime import datetime, timedelta
def analyze_usage():"""Analyze CloudRepo usage and costs."""api_url = "https://your-org.cloudrepo.io/api/v1"auth = ("admin", "password")
# Get storage metrics storage = requests.get( f"{api_url}/account/storage", auth=auth ).json()
# Get bandwidth metrics bandwidth = requests.get( f"{api_url}/account/bandwidth", params={"days": 30}, auth=auth ).json()
# Calculate costs storage_gb = storage['used'] / 1024 / 1024 / 1024 monthly_cost = calculate_monthly_cost(storage_gb)
print(f"Storage Used: {storage_gb:.2f} GB") print(f"Monthly Bandwidth: {bandwidth['total']:.2f} GB") print(f"Estimated Monthly Cost: ${monthly_cost:.2f}")
# Fill in your actual current JFrog Artifactory bill (e.g. from your latest invoice): your_jfrog_monthly_cost = 0.0 print(f"vs. JFrog Artifactory: ${your_jfrog_monthly_cost:.2f}") print(f"Monthly Difference: ${your_jfrog_monthly_cost - monthly_cost:.2f}")
analyze_usage()Security Best Practices
Implement security measures in CloudRepo:
CloudRepo provides AES-256 encryption at rest and TLS 1.2+ in transit for all artifacts. Access is managed through CloudRepo’s access token system and SCIM 2.0 user provisioning. For vulnerability scanning, integrate your CI/CD pipeline with tools like Snyk or GitHub Security Scanning to scan artifacts before or after upload.
Risk Mitigation Strategies
Parallel Run Strategy
Run both systems in parallel during transition:
import requestsimport concurrent.futures
def deploy_to_both(artifact_path, artifact_data):"""Deploy artifact to both Artifactory and CloudRepo."""results = {}
with concurrent.futures.ThreadPoolExecutor() as executor: # Deploy to Artifactory artifactory_future = executor.submit( deploy_to_artifactory, artifact_path, artifact_data )
# Deploy to CloudRepo cloudrepo_future = executor.submit( deploy_to_cloudrepo, artifact_path, artifact_data )
results['artifactory'] = artifactory_future.result() results['cloudrepo'] = cloudrepo_future.result()
return results
def deploy_to_artifactory(path, data):return requests.put(f"https://artifactory.example.com/artifactory/libs-release/{path}",data=data,auth=("user", "pass"))
def deploy_to_cloudrepo(path, data):return requests.put(f"https://your-org.mycloudrepo.io/repositories/libs-release/{path}",data=data,auth=("user", "pass"))Rollback Procedures
Prepare for potential rollback:
#!/bin/bash# Keep Artifactory in read-only mode
curl -X POST -u admin:password \ "https://artifactory.example.com/artifactory/api/system/configuration" \ -H "Content-Type: application/xml" \ -d '<config><security><anonAccessEnabled>false</anonAccessEnabled></security></config>'
# Sync recent changes back to Artifactory if needed
sync_to_artifactory() {
# Get artifacts created in last 24 hours
recent_artifacts=$(curl -s \ "$CLOUDREPO_URL/api/v1/artifacts?since=24h" \ -u $CLOUDREPO_USER:$CLOUDREPO_API_TOKEN)
# Sync back to Artifactory
for artifact in $recent_artifacts; do curl -X PUT \ "https://artifactory.example.com/artifactory/$artifact" \ -u admin:password \ -T "$artifact"done}
# Quick rollback DNS
rollback_dns() {echo "nameserver artifactory.example.com" > /etc/resolv.confsystemctl restart networking}Data Validation
Ensure data integrity throughout migration:
import hashlibimport requestsfrom pathlib import Path
def validate_artifacts(manifest_file):"""Validate all artifacts were migrated correctly."""validation_results = []
with open(manifest_file, 'r') as f: artifacts = f.readlines()
for artifact_path in artifacts: artifact_path = artifact_path.strip()
# Get checksum from Artifactory artifactory_checksum = get_artifactory_checksum(artifact_path)
# Get checksum from CloudRepo cloudrepo_checksum = get_cloudrepo_checksum(artifact_path)
# Compare if artifactory_checksum == cloudrepo_checksum: status = "✓" else: status = "✗"
validation_results.append({ "path": artifact_path, "status": status, "artifactory_sha": artifactory_checksum, "cloudrepo_sha": cloudrepo_checksum })
# Generate report success_count = sum(1 for r in validation_results if r['status'] == "✓") total_count = len(validation_results)
print(f"Validation Results: {success_count}/{total_count} artifacts match")
# Save detailed report with open('validation_report.json', 'w') as f: json.dump(validation_results, f, indent=2)
return success_count == total_count
def get_artifactory_checksum(artifact_path):response = requests.head(f"https://artifactory.example.com/artifactory/libs-release/{artifact_path}",auth=("user", "pass"))return response.headers.get('X-Checksum-SHA256', '')
def get_cloudrepo_checksum(artifact_path):response = requests.head(f"https://your-org.mycloudrepo.io/repositories/libs-release/{artifact_path}",auth=("user", "pass"))return response.headers.get('X-Checksum-SHA256', '')
# Run validation
if validate_artifacts('migration_manifest.txt'):print("✓ Migration validation successful!")else:print("✗ Validation failed - check validation_report.json")Conclusion
Migrating from JFrog Artifactory to CloudRepo is one of the most impactful decisions you can make for your organization’s bottom line and developer productivity. With flat, predictable pricing in place of consumption billing and a dramatically simplified operational model, the payoff is immediate.
Success
Key Takeaways: - Flat, all-inclusive pricing replaces consumption billing and egress fees - Migration can be completed in days, not weeks - Zero infrastructure overhead means your team focuses on shipping code - No egress fees eliminates budget uncertainty - Support included in all plans - no expensive contracts
The migration process, while requiring careful planning, is straightforward and well-supported. With 10 years of experience helping teams migrate from complex repository managers, CloudRepo’s team is available to assist with migration planning and execution, ensuring a smooth transition.
Next Steps
- Calculate Your Savings: Use our pricing calculator to see your exact savings
- Start Free Trial: Experience CloudRepo with a 14-day free trial
- Get Migration Support: Contact our team for migration assistance and scripts
Ready to escape JFrog’s complex pricing and move to flat, predictable repository costs? Start your 14-day free trial today and join the teams who’ve made the switch to CloudRepo.
Need Migration Help?
Migrating from another repository manager? I've personally helped teams make the switch. Book a call to discuss your migration plan or get personalized assistance.
For step-by-step instructions, follow the migration process outlined above. Our team is ready to help with any questions.