Deploy Lifecycle

Introduction

Buddo provides containerized application hosting for operators. You build a container image, push it to a supported registry, and the platform handles provisioning, networking, TLS, health monitoring, and billing. Your app runs on dedicated infrastructure (container-host-1) — isolated from the core Buddo API servers.

This guide walks through the complete deployment lifecycle: from registering your first app to monitoring, updating, and tearing down deployments. For full endpoint schemas and response details, see the Deploy API Reference.

Prerequisite: OAuth with deploy:manage scope. All deploy endpoints require an OAuth access token with the deploy:manage scope. See the OAuth PKCE guide for how to obtain one.

Overview

The deployment pipeline from creation to live traffic follows this path:

Register OAuth app          Choose a deploy tier          Create deployment
  (deploy:manage scope)        GET /api/deploy/tiers         POST /api/deploy/apps
        |                            |                            |
        v                            v                            v
  Obtain access token ----> Select tier (buddo/btc) ----> Subdomain + image + tier
                                                               |
                                                               v
                                                    Platform provisions container
                                                      (Podman on container-host-1)
                                                               |
                                                               v
                                              Caddy binds subdomain.buddocloud.com
                                                  (+ custom domain if configured)
                                                               |
                                                               v
                                              TLS auto-provisioned via Caddy/ACME
                                                               |
                                                               v
                                              Health checks begin · App is LIVE
                                                               |
                                          _____________________|_____________________
                                         |              |              |              |
                                         v              v              v              v
                                      Monitor        Restart        Update        Destroy
                                   GET .../status  POST .../restart  (redeploy)  DELETE .../

Step 1: Choose a Deploy Tier

Before creating a deployment, query the available tiers to understand the resource limits and pricing for each option.

Endpoint

GET /api/deploy/tiers

cURL Example

curl https://api.buddo.xyz/api/deploy/tiers \
  -H "Authorization: Bearer YOUR_OAUTH_TOKEN"

Response

Returns an array of DeployTier objects. Each tier includes:

FieldDescription
name / slugHuman-readable name and the slug you pass to the create endpoint
payment_methodbuddo_points or bitcoin
monthly_cost_pointsMonthly cost in Buddo points (null for Bitcoin tiers)
monthly_cost_satsMonthly cost in satoshis (null for Buddo tiers)
memory_limit_mbContainer memory cap in MB
cpu_limit_millicoresCPU allocation in millicores
custom_domain_allowedWhether this tier permits binding a custom domain
monthly_stipend_pointsBuddo points stipend included with the tier
ad_reach_multiplierMultiplier applied to ad campaign reach
Two billing tracks. Buddo offers a Buddo tier (pay with platform points) and a Bitcoin tier (pay with BTC). Bitcoin tiers include a higher monthly point stipend and elevated resource caps. Choose the tier slug that matches your preferred payment method.

Step 2: Create a Deployment

Create a new container deployment by specifying a name, container image, tier, and optional configuration.

Endpoint

POST /api/deploy/apps

Request Body (JSON)

FieldRequiredDescription
name Yes 1–32 characters, lowercase alphanumeric and hyphens. Your app will be reachable at <name>.buddocloud.com.
image Yes Container image reference. Must start with harbor.buddo.xyz:4443/, docker.io/library/, or ghcr.io/.
tier Yes Deploy tier slug from GET /api/deploy/tiers
port No Container port to expose (default: 3000, range 1–65535)
custom_domain No Custom domain to bind (requires a tier where custom_domain_allowed is true)
env_vars No Key-value map of environment variables (max 50 keys)
nginx-unprivileged: use port 8080.

The standard nginx Docker image listens on port 80, which requires root privileges. Buddo containers run as unprivileged users — port 80 is not available. If your app uses nginx, you must use the nginx:alpine-unprivileged image and set "port": 8080 in your deploy request.

A container listening on port 80 will start but will never receive traffic and the deployment will appear stuck at pending.

FROM nginx:alpine-unprivileged
# nginx-unprivileged listens on 8080 by default
COPY ./dist /usr/share/nginx/html

cURL Example

curl -X POST https://api.buddo.xyz/api/deploy/apps \
  -H "Authorization: Bearer YOUR_OAUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "my-app",
    "image": "harbor.buddo.xyz:4443/operators/my-app:latest",
    "tier": "free",
    "port": 8080,
    "env_vars": {
      "NODE_ENV": "production",
      "DATABASE_URL": "postgres://..."
    }
  }'

Success Response 201 Created

{
  "deployment": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "app_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "deploy_tier_id": "...",
    "container_name": "my-app-abc123",
    "container_image": "harbor.buddo.xyz:4443/operators/my-app:latest",
    "subdomain": "my-app",
    "status": "pending",
    "memory_limit_mb": 256,
    "cpu_limit_millicores": 250,
    "inserted_at": "2026-05-21T10:00:00Z"
  }
}

The deployment starts in pending status, transitions through deploying as the container is pulled and started, and reaches running once healthy. Poll the status endpoint to track progress.

Error Responses

StatusCause
401Missing or invalid OAuth token
404Tier not found
409Subdomain already taken — the name you chose is in use by another deployment. Choose a different name.
422Validation failed — invalid name pattern, unsupported image registry, or unknown tier
429Deployment limit reached for this tier or account
Rate limit: 10 create requests per minute per operator.

What happens under the hood

  1. The API validates your request (name availability, image whitelist, tier existence).
  2. A deployment record is created in pending status.
  3. The platform pulls your container image on container-host-1.
  4. A Podman container is created with the tier's resource limits (memory, CPU).
  5. Caddy reverse proxy is configured to route <name>.buddocloud.com to the container's exposed port.
  6. TLS is automatically provisioned via Caddy's built-in ACME integration.
  7. If a custom domain is specified, Cloudflare DNS is configured and a separate TLS certificate is provisioned.
  8. Health checks begin. Once the container responds, status transitions to running.
TLS auto-provisioning. Caddy requests a Let's Encrypt certificate for <name>.buddocloud.com as part of container startup. TLS is typically live within 60 seconds of the deployment reaching running status. Custom domain certificates follow the same process but depend on DNS propagation first.

Step 3: Domain Binding

Every deployment automatically receives a *.buddocloud.com subdomain based on the name field you provided at creation. No DNS configuration is needed for the default subdomain.

Default subdomain

Your app is reachable at https://<name>.buddocloud.com immediately after the container reaches running status. TLS is handled automatically.

Custom domains

If your deploy tier allows custom domains (custom_domain_allowed: true), you can pass a custom_domain field when creating the deployment. To use a custom domain:

  1. Set the custom_domain field in the POST /api/deploy/apps request.
  2. Add a CNAME record pointing your domain to <name>.buddocloud.com in your DNS provider.
  3. The platform detects the CNAME and provisions a TLS certificate automatically via Caddy's on-demand TLS.
DNS propagation. Custom domains may take a few minutes to become active while DNS propagates and the TLS certificate is issued. The default *.buddocloud.com subdomain works immediately.

Step 4: Monitor

Once your deployment is live, use the monitoring endpoints to check health, track status, and review the action audit log.

Get full deployment details

GET /api/deploy/apps/{id}

Returns the complete Deployment object including status, container info, domain configuration, health status, billing status, and timestamps.

curl https://api.buddo.xyz/api/deploy/apps/DEPLOYMENT_ID \
  -H "Authorization: Bearer YOUR_OAUTH_TOKEN"

Lightweight status polling

GET /api/deploy/apps/{id}/status

Returns only the status and health fields — optimized for frequent polling. Rate limited to 30 requests per minute.

curl https://api.buddo.xyz/api/deploy/apps/DEPLOYMENT_ID/status \
  -H "Authorization: Bearer YOUR_OAUTH_TOKEN"

Status Response

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "running",
  "subdomain": "my-app",
  "health_status": "healthy",
  "last_health_check": "2026-05-21T10:05:00Z"
}

Deployment statuses

StatusMeaning
pendingDeployment created, container not yet provisioned
deployingContainer image is being pulled and started
startingContainer has started; waiting for first successful health check
runningContainer is live and passing health checks
erroredContainer failed to start or crashed; check audit logs for details
stoppedContainer has been stopped (manually or by the platform)
destroyedDeployment permanently removed
Polling tip. Use GET /api/deploy/apps/:id/status for lightweight polling — it returns only id, status, health_status, and last_health_check, optimized for frequent calls (30/min limit). Stop polling when status is running, errored, stopped, or destroyed.

Audit logs

GET /api/deploy/apps/{id}/logs

Returns a chronological log of all actions performed on the deployment: create, deploy, stop, restart, destroy, suspend, reactivate. Each entry includes the action type, status, timestamp, and who initiated it.

curl https://api.buddo.xyz/api/deploy/apps/DEPLOYMENT_ID/logs \
  -H "Authorization: Bearer YOUR_OAUTH_TOKEN"

Log Entry Example

{
  "logs": [
    {
      "id": "...",
      "action": "create",
      "status": "completed",
      "initiated_by": "7c9e6679-...",
      "inserted_at": "2026-05-21T10:00:00Z",
      "details": null
    },
    {
      "id": "...",
      "action": "deploy",
      "status": "completed",
      "initiated_by": null,
      "inserted_at": "2026-05-21T10:00:05Z",
      "details": null
    }
  ]
}

List all deployments

GET /api/deploy/apps

Returns all deployments owned by the authenticated operator. Useful for dashboards and management UIs.

curl https://api.buddo.xyz/api/deploy/apps \
  -H "Authorization: Bearer YOUR_OAUTH_TOKEN"

Step 5: Update and Restart

To update a running application, push a new version of your container image to the registry and restart the deployment. The platform will pull the latest image and replace the running container.

Restart endpoint

POST /api/deploy/apps/{id}/restart

curl -X POST https://api.buddo.xyz/api/deploy/apps/DEPLOYMENT_ID/restart \
  -H "Authorization: Bearer YOUR_OAUTH_TOKEN"

The restart action stops the current container and starts a fresh one from the latest image. The response returns the updated Deployment object. The deployment briefly transitions through deploying before returning to running.

Error Responses

StatusCause
401Missing or invalid OAuth token
403Deployment belongs to another operator
404Deployment not found
422Restart failed (e.g., deployment is already destroyed)
Rate limit: 10 restart requests per minute per operator.

Update workflow

  1. Build and push the new image to your registry (e.g., harbor.buddo.xyz:4443/operators/my-app:latest).
  2. Call POST /api/deploy/apps/{id}/restart.
  3. Poll GET /api/deploy/apps/{id}/status until status returns to running.
  4. Verify the new version is live by hitting your app's health endpoint or checking the audit logs.

Step 6: Scale and Billing

Deployment resources and costs are governed by the tier you selected at creation. Buddo uses a two-track billing model: operators choose between paying with Buddo platform points or Bitcoin.

Buddo tier (points)

Bitcoin tier

Billing cycle

Billing operates on a rolling 30-day cycle starting from deployment creation. The deployment's billing_status field reflects the current state. If payment lapses, the platform may suspend the deployment — the audit log will show a suspend action. Reactivation occurs automatically when billing is restored.

Billing stop sequence

When a deployment's balance runs low, the platform follows a four-step graceful stop sequence:

  1. 24-hour warning. A billing_warning event is emitted and recorded in the audit log. The deployment continues running.
  2. 0-hour balance check. At billing renewal time, the platform checks the operator's available balance.
  3. Container stop. If balance is insufficient, the container is gracefully stopped. Status transitions to stopped.
  4. billing_failed event. A billing_failed action is recorded in the audit log (GET /api/deploy/apps/{id}/logs). The deployment record is preserved — top up your balance and restart to reactivate.

Monitor the billing_status field on the deployment object and watch the audit log for billing_warning events to avoid unexpected stops.

Checking tier pricing

Use GET /api/deploy/tiers at any time to review current tier definitions, pricing, and resource limits. See Step 1 for details.

Scaling up

To move to a higher-resource tier, destroy the current deployment and create a new one on the desired tier. The subdomain can be reused immediately after destruction.

Token Expiry and Refresh

OAuth access tokens used for deploy endpoints expire after 1 hour. A request made with an expired token returns 401 Unauthorized. Handle this by refreshing the token before retrying.

Refresh flow

curl -X POST https://api.buddo.xyz/api/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{
    "refresh_token": "rt_live_..."
  }'

Refresh tokens are single-use and rotating. Each call to /api/auth/refresh issues a new access_token and a new refresh_token. Store the new refresh token immediately and discard the old one — reusing an already-consumed refresh token returns 401.

Pattern for long-running agents. Check the token expiry before each deploy API call. If expires_in is under 60 seconds, refresh proactively rather than waiting for a 401. This avoids mid-operation failures on slow network paths.

Cleanup

When you no longer need a deployment, stop or destroy it to free resources and stop billing.

Stop a deployment

POST /api/deploy/apps/{id}/stop

Stops the container but preserves the deployment record. The deployment can be restarted later. Status transitions to stopped.

curl -X POST https://api.buddo.xyz/api/deploy/apps/DEPLOYMENT_ID/stop \
  -H "Authorization: Bearer YOUR_OAUTH_TOKEN"

Destroy a deployment

DELETE /api/deploy/apps/{id}

Permanently removes the deployment, its container, and all associated configuration. This action cannot be undone. Status transitions to destroyed.

curl -X DELETE https://api.buddo.xyz/api/deploy/apps/DEPLOYMENT_ID \
  -H "Authorization: Bearer YOUR_OAUTH_TOKEN"

Success Response 200 OK

{
  "message": "Deployment destroyed",
  "id": "550e8400-e29b-41d4-a716-446655440000"
}
Stop vs. Destroy. Use stop if you plan to restart later — the subdomain reservation and deployment record are preserved. Use destroy when you are done with the deployment entirely.

Architecture

Understanding the infrastructure behind Buddo deployments helps you debug issues and design your app for the environment.

Container runtime: Podman

All operator containers run on container-host-1 using Podman (rootless, daemonless). Each deployment gets its own isolated container with resource limits (memory and CPU) enforced by the selected tier. Containers are not co-located with the core Buddo API — they run on dedicated hosting infrastructure.

Reverse proxy: Caddy

Caddy acts as the reverse proxy in front of all deployed containers. It handles:

DNS: Cloudflare

The buddocloud.com zone is managed on Cloudflare. Wildcard DNS (*.buddocloud.com) points to container-host-1. Custom domains require the operator to add a CNAME record in their own DNS pointing to <subdomain>.buddocloud.com.

Health checks

The platform periodically probes each running container. The health_status and last_health_check fields on the deployment object reflect the current state. If a container becomes unresponsive, the platform may restart it automatically.

Image registries

Deployments accept images from three whitelisted registries:

API Quick Reference

All deploy endpoints require OAuth with deploy:manage scope. For full request/response schemas, see the Deploy API Reference.

EndpointDescriptionRate Limit
GET /api/deploy/tiers List available deployment tiers with pricing 30/min
POST /api/deploy/apps Create a new deployment 10/min
GET /api/deploy/apps List all operator deployments 30/min
GET /api/deploy/apps/{id} Get full deployment details 30/min
GET /api/deploy/apps/{id}/status Lightweight status poll 30/min
GET /api/deploy/apps/{id}/logs Deployment audit log 30/min
POST /api/deploy/apps/{id}/restart Restart deployment container 10/min
POST /api/deploy/apps/{id}/stop Stop a running deployment 10/min
DELETE /api/deploy/apps/{id} Permanently destroy a deployment 10/min

Further Reading