Deploy an HTTP container with YAML

Deploy an HTTP container on Bahriya with a Reis YAML manifest: workload, autoscaling, custom hostnames, observability and security, field by field.

Updated 9 Nov 20265 min read

An HTTP container is a long-running service that accepts incoming requests on a port and gets a hostname, TLS, autoscaling, rate limiting, IP allow/deny, and basic auth out of the box. Pick this type when you're deploying an API, a web app, or anything else that fronts traffic.

This guide walks through the full YAML shape for an HTTP container. For the same deployment driven from the shell, see Deploy an HTTP container with flags.

Minimal example

kind: container
project: my-project
handle: web-api
name: Web API
type: http
regions:
  - falkenstein-1
 
workload:
  image: ghcr.io/myorg/api:v1.0.0
  cpu: "500"
  memory: "512"
  port: 8080
  healthcheck: /healthz
 
networking:
  external: true

Apply it:

reis apply -f web-api.yml

That's the entire path from "empty file" to "production HTTP container."

Anatomy of the YAML

The top-level keys identify the container and place it; the workload group covers the image, runtime sizing and the HTTP-specific port and health check; networking.external decides whether the container is reachable from the internet.

kind: container
project: my-project                # the project handle this container lives under
handle: web-api                    # immutable identifier — Reis looks this up to update later
name: Web API                      # display name in the console
type: http                         # required; immutable after create
regions:
  - falkenstein-1
  - virginia-1                     # multi-region by listing more regions
 
workload:
  image: ghcr.io/myorg/api:v1.0.0  # image reference with tag
  registry: ghcr                   # registry handle if the image is private
  cpu: "500"                       # CPU in millicores; required
  memory: "512"                    # memory in MB; required
  port: 8080                       # the port your app listens on
  healthcheck: /healthz            # HTTP path that returns 200 when healthy
  protocol: http                   # http (default), https, or tcp
  env:                             # environment variables as plain key/value
    NODE_ENV: production
    LOG_LEVEL: info
  secrets:                         # ENV_VAR_NAME → secret-handle
    DB_PASSWORD: db-password
    API_KEY: api-key
 
networking:
  external: true                   # reachable from the internet at its hostnames

Reaching the container

networking.external decides who can reach an HTTP container:

  • true: the container is reachable from the internet at its hostnames.
  • false: only workloads in the same project can reach it.

State it in every HTTP container document. It has no default for HTTP containers, so a hostname is published only when the file says so: reis apply refuses to create an HTTP container without it, and warns when an update leaves it out. If an HTTP container is created through the API directly without it, the API makes the container reachable from the internet.

Autoscaling

Set max_replicas to enable autoscaling. The minimum is replicas; autoscaling works between the two based on CPU and memory targets.

scaling:
  replicas: 2
  max_replicas: 8
  target_cpu: 70         # percent of requested CPU
  target_memory: 80      # percent of requested memory

If you don't include max_replicas, the container runs at exactly replicas instances. Autoscaling cannot be enabled while persistent volumes are attached.

Custom hostnames

By default, an HTTP container gets a hostname from Bahriya. Add your own:

networking:
  external: true
  domains:
    - hostname: app.example.com
      wwwredirect: true            # serves www.app.example.com → app.example.com

The platform issues a TLS certificate for each hostname automatically once its DNS records are in place. See Custom domain setup for the records to create.

A container deployed in two or more regions can spread traffic across them on its vanity hostname with networking.custom_dns_mode:

networking:
  custom_dns_mode: geo             # none (default), round-robin or geo

A mode other than none requires two or more regions.

Observability

Expose Prometheus metrics so the platform scrapes them:

observability:
  prometheus_port: 9090
  prometheus_path: /metrics

Security

Rate limiting and IP allow/deny lists sit under security:

security:
  rate_limiting:
    enabled: true
    per_minute: 60
  basic_auth:
    - username: ops
      password: <bcrypt-or-plaintext>

Field reference

YAML pathMaps toNotes
typecontainer typerequired: http
workload.imageimage referencerequired
workload.registryregistry handlefor a private image
workload.portcontainer portdefault 8080
workload.healthcheckhealth check pathdefault /healthz
workload.protocolprotocol your app speaks on its port: http, https or tcp. Used for health checks and for delivering trafficoptional, default http
workload.cpuCPU (millicores)required to create; no default
workload.memorymemory (MB)required to create; no default
workload.envenvironment variableskey/value map
workload.secretsinjected secretsENV_NAME → secret-handle
scaling.replicasmin instancesdefault 1
scaling.max_replicasmax instancesenables autoscaling
networking.externalreachable from the internetstate it: true or false
networking.domainscustom hostnamesTLS issued automatically
networking.custom_dns_modetraffic across regions on the vanity hostnamenone (default), round-robin, geo
observability.prometheus_portmetrics portenables scraping
observability.prometheus_pathmetrics pathdefault /metrics

For the complete list of every group and field, see the YAML mode reference.

Updating an HTTP container

Re-apply the same file with any changes:

reis apply -f web-api.yml

Reis looks up the container by handle and updates it to match the file. A field the file leaves out returns to its default. If that would clear values the container holds today, such as its environment variables, or return a field you set to its default, such as the port, Reis asks first (--yes confirms in scripts). Keep the file complete, and check it with reis apply -f web-api.yml --dry-run before applying. See Applying a file that would clear or reset values.