---
title: "Add a Cloud Provider"
url: "https://docs.caracal.run/v1.0/operations/add-a-cloud-provider/"
markdown_url: "https://docs.caracal.run/markdown/v1.0/operations/add-a-cloud-provider.md"
description: "Implement Caracal on a new cloud or an internal platform by writing adapters, without changing the deployment core."
page_type: "workflow"
concepts: []
requires: []
---

# Add a Cloud Provider

Canonical URL: https://docs.caracal.run/v1.0/operations/add-a-cloud-provider/
Markdown URL: https://docs.caracal.run/markdown/v1.0/operations/add-a-cloud-provider.md
Description: Implement Caracal on a new cloud or an internal platform by writing adapters, without changing the deployment core.
Page type: workflow
Concepts: none
Requires: none

---

Caracal's deployment logic contains no cloud provider. Each deployment model has a provider-neutral core that owns the whole deployment shape, and a small adapter per provider that translates generic concepts into that provider's resources. Supporting a new cloud, or an internal platform, means writing adapters. It never means changing the core.

## What the Core Owns

The core decides everything that is a property of Caracal rather than of a cloud: which images run, which start command selects a service role, which ports they listen on, which credentials each service needs, how readiness is probed, how far each service scales, and that schema migrations complete before any service rolls.

An adapter never restates these. If you find yourself encoding a port, an image, or a service's environment in an adapter, that fact belongs in the core.

## Deployment Models

| Model | Core | Adapter |
| --- | --- | --- |
| Virtual machine | `infra/tofu/modules/caracalHost` renders cloud-init | `infra/tofu/providers/<provider>/host` creates the instance, exposure, identity, and DNS |
| Managed container platform | `infra/containerPlatform/topology.yaml` and `render.mjs` | `infra/containerPlatform/targets/<provider>.mjs` renders platform manifests |
| Kubernetes | `infra/helm/caracal` | A values overlay in `infra/helm/caracal/examples` |

## Virtual Machine Adapter

Implement one OpenTofu module that satisfies the host contract. The contract is the same on every cloud, so a caller swaps providers by changing a module source and nothing else.

Inputs: `name`, `region`, `machineSize`, `diskGb`, `userData`, `adminUsername`, `adminPublicKey`, `ingressCidrs`, `adminCidrs`, `networkCidr`, `dnsZone`, `hostnames`, `dnsTtl`, `tags`. Outputs: `publicIp`, `hostId`, `identityId`, `hostnames`.

Group any input only your provider needs after the shared contract, under a comment naming the provider. Create the instance, its network exposure, a cloud identity, and the DNS records; nothing else. Open inbound 80 alongside 443, because certificate issuance answers its challenge over plain HTTP before a certificate exists.

```hcl
module "bootstrap" {
  source         = "../../modules/caracalHost"
  caracalVersion = "v0.2.1"
  tlsProxy = {
    email  = "ops@example.com"
    routes = { "console.example.com" = "web" }
  }
}

module "host" {
  source         = "../../providers/myCloud/host"
  name           = "caracal-prod"
  region         = "region-1"
  userData       = module.bootstrap.userData
  adminPublicKey = file("~/.ssh/id_ed25519.pub")
  hostnames      = ["console.example.com"]
}
```

`bash infra/tofu/scripts/validate.sh` validates every adapter against its real provider schema and fails if any contract input or output is missing.

## Managed Container Platform Adapter

Implement one module exporting three members, then register it in `render.mjs`.

| Member | Purpose |
| --- | --- |
| `secretDelivery` | `file` when the platform can project a secret onto a filesystem, `env` when it can only bind a variable |
| `internalUrl(service, config)` | The address other services reach this one on inside the deployment. Receives `{ name, port }` |
| `render(plan, config)` | Returns a map of file name to file body |
| `experimental` | Set `true` until the adapter has run against a live account |

`secretDelivery` is the whole of the secrets abstraction. Every Caracal service accepts a credential either as a variable or through its `_FILE` form; the core reads your declaration and binds whichever the platform supports. Declare `file` and the core sets `DATABASE_URL_FILE` and hands you the list of files to project. Declare `env` and it sets `DATABASE_URL` and hands you the secret name to reference. Your adapter never chooses a variable name.

Name rendered files so they sort in apply order, with migration jobs ahead of services: `10-job-migrate`, `20-app-sts`, and so on. Ship an apply flow that runs the jobs to completion before rolling any service, because no platform sequences that for you.

Add an example deployment config under `examples/`, then run `bash infra/containerPlatform/scripts/validate.sh`. It renders every adapter through the same assertions: stock images selected by command, credentials resolved from a secret manager and never materialised, internal services not publicly exposed, and jobs sorted ahead of services.

## Kubernetes Adapter

The chart is already provider-neutral. A cloud is four values:

| Value | Concept |
| --- | --- |
| `replayPersistence.storageClassName` | Durable per-replica storage |
| `ingress.*.className` and annotations | Ingress and certificates |
| `serviceAccount.annotations` | Keyless cloud identity |
| `global.podLabels` | Any label the provider's identity webhook requires |

Copy an existing overlay from `infra/helm/caracal/examples`, substitute those values and the matching External Secrets store, and add it to the loop in the chart's validation script. A cloud that needs a chart change is a bug in the chart, not in the overlay.

## Maturity

Mark a new adapter experimental until it has been exercised against a live account. For a container platform adapter that is `experimental: true`, which makes the renderer warn on every run; elsewhere it is a note at the top of the file and a row in [Cloud Support Matrix](/v1.0/operations/cloud-support-matrix/). Rendering cleanly proves a manifest is well formed. It does not prove a deployment works.

## Next Step

Read [Cloud Support Matrix](/v1.0/operations/cloud-support-matrix/) for what each provider currently implements.
