Export from Packagecloud
To migrate your packages from Packagecloud to Buildkite Package Registries, you'll need to export and download packages from a Packagecloud repository using the Packagecloud REST API before importing them to your Buildkite registry.
Before you start
To export the packages, you'll need:
- A Packagecloud account with access to the repository you want to export
- Your Packagecloud API token
-
curlinstalled on your system -
jqinstalled for JSON processing (install usingbrew install jqon macOS orapt install jqon Debian/Ubuntu) - Sufficient disk space for your packages
Get your Packagecloud API token
- Log in to packagecloud.io.
- Navigate to packagecloud.io/api_token.
- Copy your API token and store it securely.
Export all packages from a repository
The following shell script exports all packages from a Packagecloud repository to a local directory. It handles pagination automatically and preserves the original filenames.
To be able to use the script, create a file named export-packagecloud.sh with the following content:
#!/bin/bash
set -euo pipefail
PACKAGECLOUD_TOKEN="${PACKAGECLOUD_TOKEN:-}"
PACKAGECLOUD_USER="${PACKAGECLOUD_USER:-}"
PACKAGECLOUD_REPO="${PACKAGECLOUD_REPO:-}"
OUTPUT_DIR="${OUTPUT_DIR:-./packagecloud-export}"
PER_PAGE=100
if [[ -z "$PACKAGECLOUD_TOKEN" ]]; then
echo "Error: PACKAGECLOUD_TOKEN environment variable is required"
exit 1
fi
if [[ -z "$PACKAGECLOUD_USER" ]]; then
echo "Error: PACKAGECLOUD_USER environment variable is required"
exit 1
fi
if [[ -z "$PACKAGECLOUD_REPO" ]]; then
echo "Error: PACKAGECLOUD_REPO environment variable is required"
exit 1
fi
mkdir -p "$OUTPUT_DIR"
echo "Exporting packages from packagecloud.io/${PACKAGECLOUD_USER}/${PACKAGECLOUD_REPO}"
echo "Output directory: $OUTPUT_DIR"
fetch_all_packages() {
local page=1
local all_packages="[]"
while true; do
echo "Fetching page $page..."
response=$(curl -s -u "${PACKAGECLOUD_TOKEN}:" \
"https://packagecloud.io/api/v1/repos/${PACKAGECLOUD_USER}/${PACKAGECLOUD_REPO}/packages.json?per_page=${PER_PAGE}&page=${page}")
if ! echo "$response" | jq -e 'type == "array"' > /dev/null 2>&1; then
echo "Error: Invalid API response on page $page"
echo "$response"
exit 1
fi
count=$(echo "$response" | jq 'length')
if [[ "$count" -eq 0 ]]; then
break
fi
echo "Found $count packages on page $page"
all_packages=$(echo "$all_packages" "$response" | jq -s 'add')
if [[ "$count" -lt "$PER_PAGE" ]]; then
break
fi
page=$((page + 1))
done
echo "$all_packages"
}
packages=$(fetch_all_packages)
total=$(echo "$packages" | jq 'length')
echo "Total packages to download: $total"
echo "$packages" | jq '.' > "${OUTPUT_DIR}/manifest.json"
echo "Package manifest saved to ${OUTPUT_DIR}/manifest.json"
echo "$packages" | jq -c '.[]' | while read -r package; do
filename=$(echo "$package" | jq -r '.filename')
package_url=$(echo "$package" | jq -r '.package_url')
package_type=$(echo "$package" | jq -r '.type')
type_dir="${OUTPUT_DIR}/${package_type}/${PACKAGECLOUD_REPO}"
mkdir -p "$type_dir"
output_path="${type_dir}/${filename}"
if [[ -f "$output_path" ]]; then
echo "Skipping (already exists): $filename"
continue
fi
echo "Downloading: $filename"
package_details=$(curl -s -u "${PACKAGECLOUD_TOKEN}:" \
"https://packagecloud.io${package_url}")
download_url=$(echo "$package_details" | jq -r '.download_url // empty')
if [[ -z "$download_url" ]]; then
echo " Warning: No download URL found for $filename, skipping"
continue
fi
if curl -s -L -u "${PACKAGECLOUD_TOKEN}:" -o "$output_path" "$download_url"; then
echo " Saved to: $output_path"
else
echo " Error: Failed to download $filename"
rm -f "$output_path"
fi
done
echo "Export complete. Output directory: $OUTPUT_DIR"
Make the script executable and run it:
chmod +x export-packagecloud.sh
export PACKAGECLOUD_TOKEN="your-api-token"
export PACKAGECLOUD_USER="your-username"
export PACKAGECLOUD_REPO="your-repository"
./export-packagecloud.sh
The script creates the following directory structure, organizing packages by ecosystem type and source repository:
packagecloud-export/
├── manifest.json
├── deb/
│ └── my-repo/
│ └── example_1.0.0_amd64.deb
├── rpm/
│ └── my-repo/
│ └── example-1.0.0-1.x86_64.rpm
└── gem/
└── my-repo/
└── example-1.0.0.gem
Each top-level folder (deb/, rpm/, gem/) maps to one package ecosystem. The repository subdirectory preserves the source Packagecloud repository name, which can be used for re-creating these as registries within Buildkite Package Registries.
For example, to import all Debian packages into a Buildkite Debian registry, run:
find ./packagecloud-export/deb -name "*.deb" -exec bk package push my-debian-registry {} \;
Export packages manually
For smaller repositories or if you would like to have more control over the export process, you can use curl commands directly. Follow the instructions and commands in the sections below.
List all packages in a repository
curl -s -u "YOUR_API_TOKEN:" \
"https://packagecloud.io/api/v1/repos/USERNAME/REPO/packages.json?per_page=100" \
| jq '.'
Replace YOUR_API_TOKEN, USERNAME, and REPO with your values. Note the trailing colon after the token as it is required for HTTP basic authentication with an empty password.
Get package details and download URL
The package list response includes a package_url field. Use this to fetch the package details, which contain the download_url:
curl -s -u "YOUR_API_TOKEN:" \
"https://packagecloud.io/api/v1/repos/USERNAME/REPO/package/TYPE/DISTRO/VERSION/FILENAME.json" \
| jq '.download_url'
Download a package
curl -L -u "YOUR_API_TOKEN:" \
-o "package-filename.deb" \
"DOWNLOAD_URL"
Handling pagination
The Packagecloud API returns a maximum of 100 packages per request. For repositories with more packages, use the page query parameter:
curl -s -u "YOUR_API_TOKEN:" \
"https://packagecloud.io/api/v1/repos/USERNAME/REPO/packages.json?per_page=100&page=2"
The API provides pagination information in response headers:
-
Total: total number of packages -
Per-Page: number of packages per page -
Link: links to next, previous, and last pages
Troubleshooting
This section covers the potential issues you might run into when bulk-exporting your packages from Packagecloud following the instructions in this guide and how to solve them.
Authentication errors
If you receive a 401 Unauthorized response, verify that:
- Your API token is correct
- The token is passed as the username with an empty password (note the trailing colon in
-u "TOKEN:")
Rate limiting
Packagecloud may rate limit API requests. If you encounter rate limiting:
- Add a delay between downloads by inserting
sleep 1in the download loop - Run the export during off-peak hours
Missing download URLs
Some package types use different API endpoints. If a package doesn't have a download_url in the response, check the Packagecloud API documentation for the correct endpoint for that package type.
Non-version-agnostic packages
For deb, rpm, and alpine packages, migration works only if your packages are distribution version-agnostic (for example, a package works on all Ubuntu versions such as Focal and Jammy). If your packages target specific distribution versions, contact Buildkite support before proceeding.
Next step
Once you have downloaded your packages from your Packagecloud repositories, learn how to import them into your Buildkite registry.
Buildkite Package Registries signs repository metadata with its own keys, not your Packagecloud keys. After migration, update your clients (apt, yum, apk) to use the new signing keys from your Buildkite registry.