Self-Hosted & Offline

C3X can run entirely on your own infrastructure with zero external network calls. This guide covers two approaches: running a self-hosted pricing API server and using the fully offline local database mode.

Self-Hosted Pricing API

Why Self-Host?

  • Data sovereignty: Keep all pricing queries and Terraform metadata within your network.
  • Air-gapped environments: Run in environments without internet access after the initial scrape.
  • No API key required: No need to manage external API keys or worry about third-party rate limits.
  • Low latency: Pricing lookups hit a local PostgreSQL database, making estimates significantly faster.

Docker Compose Setup

The c3x-pricing-api repository provides a ready-to-use Docker Compose configuration with PostgreSQL and the API server. Clone it and start the service:

git clone https://github.com/c3xdev/c3x-pricing-api.git
cd c3x-pricing-api

# Create a .env file with your database password
echo "POSTGRES_PASSWORD=changeme" > .env

# Start PostgreSQL and the API server
docker compose up -d

The API server starts on port 4000 and automatically applies the database schema on first boot. The docker-compose.yml includes PostgreSQL 16 with health checks and automatic restarts:

services:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: ${POSTGRES_USER:-c3x}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}
      POSTGRES_DB: ${POSTGRES_DB:-c3x_pricing}
    ports:
      - "127.0.0.1:5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-c3x}"]
      interval: 2s
      timeout: 5s
      retries: 5

  api:
    build: .
    command: ["serve"]
    environment:
      PORT: "4000"
      DATABASE_URL: "postgres://${POSTGRES_USER:-c3x}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-c3x_pricing}?sslmode=disable"
      API_KEY: ${API_KEY:-}
      GCP_API_KEY: ${GCP_API_KEY:-}
      SCRAPE_CONCURRENCY: ${SCRAPE_CONCURRENCY:-4}
      MAX_BATCH_SIZE: ${MAX_BATCH_SIZE:-500}
    ports:
      - "4000:4000"
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

volumes:
  pgdata:

Environment Variables

VariableDescriptionDefault
DATABASE_URLPostgreSQL connection string.Set in compose
PORTPort the API server listens on.4000
API_KEYOptional API key for authentication. Leave empty to allow unauthenticated access.
GCP_API_KEYGoogle Cloud API key for scraping GCP pricing. Required only if you scrape GCP.
SCRAPE_CONCURRENCYNumber of concurrent scrape workers per vendor.4
MAX_BATCH_SIZEMaximum number of GraphQL queries in a single batch request.500
CORS_ALLOWED_ORIGINSComma-separated list of allowed CORS origins. Use * for any origin.*
TRUSTED_PROXIESComma-separated CIDRs or IPs of trusted reverse proxies for accurate client IP detection.

Running C3X Against the Self-Hosted API

Point the C3X CLI to your self-hosted API:

export C3X_PRICING_API_ENDPOINT=http://localhost:4000
c3x estimate --path .

You can also configure the endpoint permanently:

c3x config set pricing_api_endpoint http://localhost:4000

Scraping Pricing Data

The pricing database needs to be populated before the API can serve accurate estimates. The pricing API binary includes a built-in scraper that downloads pricing data directly from AWS, Azure, and GCP:

# Scrape all providers (~12 minutes on first run)
docker compose exec api ./c3x-pricing-api scrape --vendor aws,azure,gcp

# Scrape a single provider
docker compose exec api ./c3x-pricing-api scrape --vendor aws

The initial scrape for all three providers takes approximately 12 minutes. AWS uses bulk pricing JSON downloads, Azure queries the Retail Prices API, and GCP queries the Cloud Billing Catalog API.

Note: GCP scraping requires a GCP_API_KEY environment variable with the Cloud Billing API enabled. AWS and Azure scraping requires no credentials.

Updating Pricing Data

Cloud provider pricing changes occasionally. Re-run the scraper periodically to keep your database current:

# Cron job example (runs weekly on Sunday at 2 AM)
0 2 * * 0 cd /opt/c3x-pricing-api && docker compose exec api ./c3x-pricing-api scrape --vendor aws,azure,gcp

Each scrape run is tracked in the database with timestamps and product counts. Stale products from previous runs are automatically cleaned up.

Offline Mode

For environments where you cannot run a separate API server, C3X supports a fully offline mode using a local SQLite database. This is ideal for developer laptops, CI runners in restricted networks, or quick evaluations.

Syncing the Local Database

Download pricing data to your local machine. This requires a one-time internet connection.

# Sync AWS and Azure (default providers)
c3x pricing sync

# Sync all providers including Google Cloud
c3x pricing sync --providers aws,azure,google

# Sync only the providers you need
c3x pricing sync --providers aws

The database is stored at ~/.c3x/pricing.db by default. You can override this with the --db-path flag.

Running Estimates Offline

Once the database is synced, use the --offline flag to ensure zero network calls are made:

c3x estimate --path . --offline

In offline mode, C3X reads pricing data exclusively from the local SQLite database. If a resource type is not found in the database, it will be marked as skipped in the output.

Checking Database Status

Verify your local pricing database is populated and check when it was last updated:

c3x pricing status

Example output:

Provider    Records    Last synced
--------    -------    ------------------
aws         487,231    2026-03-15 08:12:00
azure       198,442    2026-03-15 08:18:00
google       67,103    2026-03-15 08:21:00

When to Update

Cloud provider pricing changes infrequently for most resource types. A monthly re-sync is sufficient for most teams. Consider re-syncing when:

  • You start using a new resource type and get "skipped" warnings.
  • A cloud provider announces pricing changes for resources you use.
  • New regions or instance types are launched that you plan to adopt.
  • It has been more than 30 days since the last sync.

Choosing Between Self-Hosted API and Offline Mode

ScenarioRecommendation
Team of 5+ engineers sharing a CI/CD pipelineSelf-hosted API: shared PostgreSQL database, single update process, fast GraphQL lookups.
Single developer or small teamOffline mode: simpler setup, no server to maintain.
Fully air-gapped environmentSelf-hosted API: scrape on a machine with internet, then move the Docker volume or PostgreSQL dump to the isolated network.
CI runners with no persistent storageSelf-hosted API: runners query the API over the network, no local database needed.

Next Steps

  • CLI Reference: Full documentation for pricing sync, pricing status, and the --offline flag.
  • CI/CD Integration: Use the self-hosted API or offline mode in your CI pipelines.
  • Supported Resources: See which resources are available in the pricing database.