A common yet flawed approach to CI/CD platform selection is comparing list prices or advertised per-minute rates. These numbers are often misleading because they fail to account for the specific resource profiles, concurrency patterns, and pipeline structures of your actual workload. A more rigorous method involves programmatically analyzing costs using the vendors' own public pricing APIs, which expose the actual SKUs and pricing dimensions that will determine your invoice.
The core principle is to model your pipeline not as a monolithic block of time, but as a sequence of jobs, each requiring specific resource types (e.g., CPU, memory, OS) for a measured duration. You then query the pricing API for the applicable rates and calculate a total. This allows for accurate comparisons between platforms with fundamentally different pricing models (e.g., per-user + concurrent job minutes vs. compute-unit-seconds).
Here is a conceptual framework for the comparison, followed by a practical example using a simplified Python script.
**Framework: Key Variables to Model**
* **Pipeline Definition:** Break down a representative pipeline (e.g., your main branch build). For each job, define:
* `job_type`: e.g., `linux.x86_64.2xlarge`, `windows.large`, `macos.m1.large`
* `estimated_duration_seconds`: Historical average from your current system.
* `concurrency_profile`: Does this job run in parallel with others, affecting required concurrent seats or agents?
* **Platform-Specific Dimensions:** Map your job types to the vendor's resource classes and extract their cost per unit time.
* **Ancillary Costs:** Model costs for network egress, storage (caches, artifacts), and any required per-seat licenses.
**Example: Fetching and Calculating Costs for a Linux Job**
The following script demonstrates fetching the price for a specific GitHub Actions runner type and calculating the cost for a known duration. A full implementation would iterate over a list of jobs and sum them.
```python
import requests
import json
# GitHub Actions Pricing API endpoint (public, no auth required for list)
PRICING_URL = "https://api.github.com/marketplace_listing/plans"
def get_github_actions_price_per_minute(runner_type="linux", size="medium"):
# Note: GitHub's API doesn't directly give per-minute cost.
# This requires mapping their published SKU to a known rate.
# For demonstration, we use a hardcoded mapping from public docs.
pricing_map = {
("linux", "medium"): 0.008, # $ per minute for standard GitHub-hosted runner
("windows", "medium"): 0.016,
("macos", "medium"): 0.08,
}
return pricing_map.get((runner_type, size), None)
def calculate_job_cost(price_per_minute, duration_seconds):
if not price_per_minute:
return None
minutes = duration_seconds / 60.0
return price_per_minute * minutes
# Example job: Linux build, 300 seconds (5 minutes)
runner = "linux"
size = "medium"
duration_sec = 300
price_per_min = get_github_actions_price_per_minute(runner, size)
if price_per_min:
cost = calculate_job_cost(price_per_min, duration_sec)
print(f"Job Cost: ${cost:.4f} for {duration_sec}s on {runner}/{size}")
else:
print("Runner type/size not found in pricing map.")
```
For a complete analysis, you would need to integrate with APIs from other vendors (e.g., AWS CodeBuild's `pricing-*` API, GitLab's REST API for CI/CD minutes, or CircleCI's GraphQL API). The implementation complexity increases as you model:
1. Caching efficiency and its impact on job duration.
2. Parallel job execution and its effect on concurrent capacity requirements.
3. Free tier allowances and how they apply to your workload.
Ultimately, this API-driven method shifts the comparison from speculative spreadsheet estimates to a deterministic, code-based model that can be version-controlled and updated alongside pricing changes. It forces explicit assumptions about your pipeline's resource consumption, which is valuable even if you later choose a platform for non-cost reasons.
brianh
That's a really useful framework. One nuance I've encountered is that the metadata for actual resource consumption, especially duration, isn't always trivial to capture accurately. You need consistent, historical data from a current runner environment.
What's your suggested method for gathering that baseline duration data? Are you instrumenting the pipelines to log job-level metrics, or relying on the platform's existing telemetry exports? I've found discrepancies between a platform's reported job runtime and the billing granularity, which could throw off the API cost query.