> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom cloud providers

> Register your own cloud target so `praisonai deploy` can use it

Ship a package that registers under the `praisonai.deploy.providers` entry-point group and `praisonai deploy` will list, validate, and run it just like a built-in.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    PKG[📦 Your package] --> EP[praisonai.deploy.providers<br/>entry point]
    EP --> REG[CloudProviderRegistry]
    REG --> CLI[praisonai deploy<br/>--provider name]
    CLI --> HOOKS[deploy / doctor / plan<br/>status / destroy]

    classDef pkg fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef ep fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef reg fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef out fill:#10B981,stroke:#7C90A0,color:#fff

    class PKG pkg
    class EP,REG ep
    class CLI reg
    class HOOKS out
```

## Quick Start

<Steps>
  <Step title="Write a provider class">
    Implement the five methods and accept a `CloudConfig` in `__init__`.

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonai_deploy.models import (
        CloudConfig, DeployResult, DeployStatus, DestroyResult, ServiceState
    )
    from praisonai_deploy.doctor import DoctorReport, DoctorCheckResult


    class HetznerProvider:
        def __init__(self, config: CloudConfig):
            self.config = config

        def deploy(self) -> DeployResult:
            return DeployResult(success=True, message="Deployed to Hetzner")

        def doctor(self) -> DoctorReport:
            return DoctorReport(checks=[
                DoctorCheckResult(name="Hetzner token", passed=True, message="HCLOUD_TOKEN set"),
            ])

        def plan(self) -> dict:
            return {"provider": "hetzner", "region": self.config.region}

        def status(self) -> DeployStatus:
            return DeployStatus(state=ServiceState.RUNNING, provider="hetzner")

        def destroy(self, force: bool = False) -> DestroyResult:
            return DestroyResult(success=True, message="Destroyed")
    ```
  </Step>

  <Step title="Register it in pyproject.toml">
    ```toml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    [project.entry-points."praisonai.deploy.providers"]
    hetzner = "acme_praison.provider:HetznerProvider"
    ```
  </Step>

  <Step title="Install and verify">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    pip install acme-praison
    python -c "from praisonai_deploy import list_cloud_providers; print(list_cloud_providers())"
    ```
  </Step>

  <Step title="Use it in agents.yaml">
    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    deploy:
      type: cloud
      cloud:
        provider: hetzner
        region: fsn1
        service_name: my-service
    ```
  </Step>

  <Step title="Deploy">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    praisonai deploy --file agents.yaml
    ```
  </Step>
</Steps>

***

## How It Works

`praisonai deploy` validates the provider name against the registry, then resolves your class and calls its hooks.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant CLI as praisonai deploy
    participant Coerce as coerce_cloud_provider
    participant Reg as CloudProviderRegistry.default()
    participant Prov as HetznerProvider
    participant Cloud

    User->>CLI: praisonai deploy --provider hetzner
    CLI->>Coerce: validate "hetzner"
    Coerce->>Reg: known name?
    Reg-->>Coerce: yes
    CLI->>Reg: resolve("hetzner")
    Reg-->>CLI: HetznerProvider class
    CLI->>Prov: deploy()
    Prov->>Cloud: create service
    Cloud-->>Prov: url
    Prov-->>CLI: DeployResult
```

| Hook | Called by | Purpose |
| - | - | - |
| `deploy()` | `praisonai deploy run` | Create or update the service |
| `doctor()` | `praisonai deploy doctor --provider <name>` | Report readiness / credential checks |
| `plan()` | `praisonai deploy plan` | Return the planned configuration without executing |
| `status()` | `praisonai deploy status` | Report current state |
| `destroy(force=False)` | `praisonai deploy destroy` | Tear the service down |

***

## The Provider Contract

Return the same models the built-in providers return. Signatures below match `praisonai_deploy/providers/base.py`.

| Method | Signature | Returns |
| - | - | - |
| `__init__` | `__init__(self, config: CloudConfig)` | — |
| `deploy` | `deploy(self)` | `DeployResult` |
| `doctor` | `doctor(self)` | `DoctorReport` |
| `plan` | `plan(self)` | `dict` |
| `status` | `status(self)` | `DeployStatus` |
| `destroy` | `destroy(self, force: bool = False)` | `DestroyResult` |

<Tip>
  Subclass `praisonai_deploy.providers.BaseProvider` to inherit the abstract contract and let Python flag any method you forget to implement.
</Tip>

***

<Note>
  **Built-in provider names are protected.** Publishing an entry point whose name (case-insensitive) matches a built-in deploy provider — `aws`, `azure`, `gcp`, `fly`, `railway`, `render` — is skipped, and the built-in is kept (a `DEBUG` line notes the collision). The rule applies whether the deploy registry comes from `praisonai-code` (guard from [PR #4176](https://github.com/MervinPraison/PraisonAI/pull/4176)) or from a standalone `praisonai-deploy` install (guard from [PR #4184](https://github.com/MervinPraison/PraisonAI/pull/4184)). Use runtime `CloudProviderRegistry.default().register("aws", …, override=True)` for a deliberate override. See [Plugin Precedence](/docs/features/plugin-precedence).
</Note>

***

## Runtime Registration

For tests or notebooks that can't install a package, register the class directly on the shared registry.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonai_deploy.providers import CloudProviderRegistry

CloudProviderRegistry.default().register("hetzner", HetznerProvider)
```

***

## Validation Behavior

Provider names are normalised with `.strip().lower()`, so `HETZNER`, `hetzner`, and `"  hetzner  "` all resolve. Unknown names raise `ValueError: Invalid cloud provider: X. Must be one of: …`, where the list comes from the registry. Built-ins keep their enum identity (`config.provider is CloudProvider.AWS`); plugin providers are normalised strings.

***

## Common Patterns

Deploy programmatically with a plugin provider.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonai_deploy import Deploy, DeployConfig, DeployType
from praisonai_deploy.models import CloudConfig

config = DeployConfig(
    type=DeployType.CLOUD,
    cloud=CloudConfig(provider="hetzner", region="fsn1", service_name="my-service"),
)
result = Deploy(config).deploy()
print(result.url)
```

List every provider for `deploy doctor`-style tooling.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonai_deploy import list_cloud_providers

for name in list_cloud_providers():
    print(name)  # built-ins first, plugins appended sorted
```

Read `DeployStatus.provider` on a result from a plugin.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonai_deploy import Deploy

status = Deploy.from_yaml("agents.yaml").status()
print(status.provider)  # "hetzner"
```

***

## Best Practices

<AccordionGroup>
  <Accordion title="Return the built-in shapes">
    Return the same `DeployResult`, `DeployStatus`, and `DestroyResult` models the built-ins return, so any tool that parses `DeployStatus` keeps working.
  </Accordion>

  <Accordion title="Fail closed in doctor()">
    No credentials means a `DoctorCheckResult(passed=False, ...)`. A report where every check passes should guarantee a deploy can start.
  </Accordion>

  <Accordion title="Prefix the entry-point name with your vendor">
    Use a vendor-prefixed name like `acme-hetzner` to avoid collisions with other plugins.
  </Accordion>

  <Accordion title="Prefer a vendor-prefixed name over a built-in">
    A built-in name (`aws`, `azure`, `gcp`, `fly`, `railway`, `render`) is reserved — an entry point matching one is skipped, not honoured. Ship `acme-aws` instead, or use runtime `register(..., override=True)` when overriding a built-in is deliberate.
  </Accordion>
</AccordionGroup>

***

## Related

<CardGroup cols={2}>
  <Card title="Overview" icon="rocket" href="/docs/docs/features/deploy/overview">
    Deploy types and providers
  </Card>

  <Card title="CLI Reference" icon="terminal" href="/docs/docs/features/deploy/cli">
    Every praisonai deploy subcommand
  </Card>

  <Card title="Python API" icon="code" href="/docs/docs/features/deploy/python-api">
    Deploy class and programmatic APIs
  </Card>

  <Card title="Config Reference" icon="sliders" href="/docs/docs/features/deploy/config-reference">
    Every DeployConfig field and default
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.