Skip to content

Configuration

All infrastructure is defined in a single managed.yaml file in your project root. This file can be checked into version control.

Providers

Configure cloud provider credentials and defaults under providers. Each provider includes its credentials plus default region, image, and size for new servers:

yaml
providers:
  digitalocean:
    api_token: "${DIGITALOCEAN_TOKEN}"
    region: nyc3
    image: ubuntu-24-04-x64
    size: s-2vcpu-4gb
FieldDescription
api_token, etc.Provider-specific credentials (see Providers)
regionDefault datacenter region for this provider
imageDefault OS image slug
sizeDefault server size slug

Environment variables are interpolated with ${VAR} or ${VAR:-default} syntax.

See Providers for setup guides for each supported provider.

Defaults

Set which provider to use by default when creating new servers:

yaml
defaults:
  provider: digitalocean
FieldDescription
providerCloud provider to use for new servers

Servers

The servers section lets you name servers and control where services run. This is useful when you want to put multiple services on the same server, or use your own existing servers instead of having managed create new ones.

yaml
servers:
  my-vps:
    ip: 203.0.113.10
    ssh_user: deploy

  shared-box:
    provider: digitalocean
    size: s-2vcpu-4gb

There are two kinds of server entries:

External servers (bring your own)

Set ip to use a server you already have. Managed will SSH into it and install services, but will never create or destroy it.

yaml
servers:
  my-vps:
    ip: 203.0.113.10
FieldDescription
ipIP address of your existing server
ssh_userUser to SSH as (default: root)

Keeping a server's existing ports open

When you adopt a server that already serves traffic, turning managed's firewall on could cut off a port that nothing in managed knows about, a custom app, a metrics exporter, an internal tool. Declare those ports under firewall.allow so managed keeps them open:

yaml
servers:
  crawler-1:
    ip: 203.0.113.10
    firewall:
      allow:
        - port: 13337            # from defaults to "private"
        - port: 9100
          from: overlay
        - port: 443
          from: public
FieldDescription
portThe port to keep open (1, 65535)
prototcp (default) or udp
fromWho may reach it: private (your own network, the default), overlay (the encrypted mesh only), public (the internet), or a literal IP/CIDR like 10.0.0.0/8

You don't need to list ports that belong to a managed service, those get their rules from the service package. firewall.allow is only for ports managed doesn't already manage. Run managed firewall import <server> to have managed suggest these rules from what the box is actually listening on, or use the dashboard's Adopt wizard. See Adopting an existing fleet.

Named managed servers

Set provider to have managed create the server for you with a stable name you choose. This is useful when you want multiple services on one server.

yaml
servers:
  app-server:
    provider: digitalocean
    size: s-2vcpu-4gb
    region: nyc3
FieldDescription
providerCloud provider to create the server on
sizeServer size slug
regionDatacenter region
imageOS image slug
tagsTags for server grouping

A server entry cannot have both ip and provider, it's either external or managed-provisioned.

Controlling which services may run on a server

Two optional flags on a server entry keep other services off a box you care about:

yaml
servers:
  renderer-1:
    ip: 203.0.113.10
    reserved_for: [html-renderer] # only this service may run here
  db-main:
    ip: 203.0.113.11
    exclusive: true # never picked automatically — a service must name it with `server:`
FieldDescription
reserved_forList of service names allowed on this server. Deploying any other service onto it fails with a clear error.
exclusiveWhen true, managed never picks this server automatically when choosing where a service runs. The only way onto it is naming it directly with server:.

Both flags attach to a single named server, not a range like web[1-3]. An exclusive server also can't be part of a pool a service spreads across (placement.servers) — point at it with server: instead. You can edit both from the dashboard on the server's settings page.

Services

The services section defines what you want running. Each service references a package and optionally sets configuration:

yaml
services:
  production-database:
    package: mysql
    replicas: 1
FieldDescription
packageName of the service package to use (resolved from your configured repositories — see Repos)
replicasNumber of replica nodes (package-specific, used by packages that declare count_from: replicas)
serverNamed server to run on (from the servers section)
protectWhen true, this service's servers are never auto-destroyed by scale-down (see below)
configPackage-specific parameters passed through to service scripts via managed.params

Custom settings belong under the config key. Top-level fields like package, provider, replicas, backups, size, region, server, servers, and protect are reserved.

Protecting a service from destruction

Set protect: true to make sure a service's servers are never torn down by an automatic scale-down, even when you run managed apply --allow-destroy or --force. Use it for anything you can't afford to lose by accident, like a primary database:

yaml
services:
  production-database:
    package: mysql
    protect: true

A protected server is left running whenever a scale-down would otherwise remove it; managed plan shows it as kept. Protection guards the automatic path only, an explicit managed destroy or managed service destroy still removes the server. You can also set protect: true on a named entry in the servers section to protect that specific box.

Pinning a service to a named server

Use server to run a service on a specific named server:

yaml
servers:
  app-server:
    provider: digitalocean
    size: s-2vcpu-4gb

services:
  database:
    package: mysql
    server: app-server

  cache:
    package: redis
    server: app-server

Both services run on the same server. Managed creates app-server once and installs both.

WARNING

server and replicas cannot be used together. A named server runs a single instance of the service. For replicated services, use auto-provisioning (omit server).

Using your own server

Combine servers with ip and server on the service:

yaml
servers:
  my-vps:
    ip: 203.0.113.10

services:
  database:
    package: mysql
    server: my-vps

Managed will SSH into 203.0.113.10 and install MySQL. It will never destroy this server, running managed destroy removes the service but leaves the server alone.

Auto-provisioned services

If you omit server, managed creates a new server automatically (this is the default behavior):

yaml
services:
  production-database:
    package: mysql
    replicas: 1

Agent

managed installs a small control agent on each server so the dashboard can read service status and run actions without SSH. On a connected project the agent dials out to the dashboard over an encrypted connection and answers its queries through that — no port on your server is opened to the internet, and it works even behind NAT or a strict company firewall.

The optional agent section tunes it:

yaml
agent:
  allow_from:
    - overlay          # allow direct access from the private network
    - 203.0.113.10     # ...plus a monitoring box of yours
FieldDescription
disabledSet true to skip installing the agent entirely
portPort the agent listens on (default 55555)
allow_fromWho may reach the agent port directly. Each entry is a keyword, public, private, or overlay (the private network), or a literal IP/CIDR like 203.0.113.10 or 198.51.100.0/24

By default the control port is closed. The dashboard talks to your servers through the agent's outbound connection, so nothing needs to dial in — and the next apply removes the old "open to everyone" rule that earlier versions added. Set allow_from only if something of yours needs to call the agent directly; managed then opens the port to exactly those sources (and the agent listens beyond the local machine). A private-only scope (overlay/private or specific IPs) is the recommended posture if you do.

Repos

Configure the repositories that service packages are installed from. Packages are resolved from the repos in the order listed:

yaml
repos:
  - repo.managed.works
  - github.com/my-org/my-services
  - url: github.com/another-org/services
    ref: v2.0.0

Repos can be specified as a plain URL string or as an object with url and ref fields for pinning to a specific git tag, branch, or commit. repo.managed.works is the official package repository — its downloads are signature-verified automatically.

There is no default repository: if repos is omitted, only repositories you've added with managed repo add <url> are searched, and with none configured managed apply can't resolve any package. Most projects should list repo.managed.works.

Full example

yaml
providers:
  digitalocean:
    api_token: "${DIGITALOCEAN_TOKEN}"
    region: nyc3
    image: ubuntu-24-04-x64
    size: s-2vcpu-4gb

defaults:
  provider: digitalocean

repos:
  - repo.managed.works

servers:
  my-vps:
    ip: 203.0.113.10

  shared:
    provider: digitalocean
    size: s-2vcpu-4gb

services:
  production-database:
    package: mysql
    server: my-vps
    config:
      root_password: "${MYSQL_ROOT_PASSWORD}"

  cache:
    package: redis
    server: shared

  app:
    package: hermes
    server: shared

  staging-database:
    package: mysql
    replicas: 1