Ecology

DeployAlly organizes services into an ecosystem of six archetypes — a closed enum describing the nature of each container. The archetype determines defaults for network, healthcheck, scope, and exposure. On top of archetypes, two mechanisms describe relationships between services: needs (consuming an asset) and provides (offering functions to consumers).

The Six Archetypes

Every template declares one — and only one — of these values in identity.archetype.

identity:
  archetype: application  # | asset | static | worker | multi_component_saas | network_appliance

1. `application`

Single app with an HTTP route. The default case for CMSs, dashboards, web tools, and backends.

Aspect Default
Network public (reachable via Traefik)
Healthcheck external_http
Scope shared (multiple instances possible on the same host)
Routes one HTTP route on the first declared internal port

Example: Memos, WordPress, n8n, Vaultwarden.

identity:
  slug: memos
  archetype: application
routes:
  - { id: web, kind: http, port: 5230, host: ${input.WEB_HOSTNAME} }

2. `asset`

Shared infrastructure service — databases, caches, queues. Singleton per host: a single MariaDB serves N instances of WordPress, n8n, Mautic. Assets have dual networking: a dedicated network (mariadb) where consumers attach, and the public network so admin tools (Adminer, dbgate) can reach them too.

Aspect Default
Network [<slug>, public] (dedicated + public)
Healthcheck external_tcp on the declared port
Scope singleton
Routes empty (no HTTP ingress)

Assets expose functions via provides.functions so consumer apps can provision isolated resources at deploy time.

Example: MariaDB, MySQL, PostgreSQL, Redis.

identity:
  slug: mariadb
  archetype: asset
  scope: singleton
routes: []
provides:
  exports:
    - { from: container.name, to: host }
    - { from: secrets.MARIADB_ROOT_PASSWORD, to: admin_credentials }
  functions:
    create_user_and_db:
      params:
        db_name:  { type: identifier, required: true }
        user:     { type: identifier, required: true }
        password: { type: secret, required: true }
      command:
        kind: docker_exec
        target: ${self.container.name}
        argv: [mariadb, -uroot, -p${self.admin_credentials}, -e,
               "CREATE DATABASE IF NOT EXISTS `${db_name}`; …"]

3. `static`

Static site served by nginx or caddy. Stateless, no needs, no secrets. The simplest archetype.

Aspect Default
Network public
Healthcheck external_http on /
Scope shared
Routes one HTTP route on /

Example: landing pages, static docs, Jekyll/Hugo sites.

4. `worker`

Headless process — no HTTP port, no route. Runs in the background processing queues, jobs, or cron tasks. Since there's no port to probe, the healthcheck observes process state.

Aspect Default
Network public (to reach external APIs)
Healthcheck stable_state — 30s settle, container must remain running
Scope shared
Routes empty

Workers require lifecycle.post_deploy.assert_stable_state by default — deploy only succeeds if the process does not crash within 30s.

Example: Postal workers, n8n queue processors, cron containers.

5. `multi_component_saas`

App with multiple containers composing a single service — typically a web + one or more workers + auxiliary dependencies. Each component becomes a separate container, but the set is deployed/decommissioned as a unit.

Aspect Default
Network public (+ extra networks per needs)
Healthcheck per component
Scope shared
Routes reference the component via component: <id>
identity:
  archetype: multi_component_saas
components:
  - id: web
    primary: true
    image: postal/postal:3.3.4
    command: [postal, web-server]
  - id: worker
    image: postal/postal:3.3.4
    command: [postal, worker]
routes:
  - id: web
    kind: http
    component: web
    port: 5000
    host: ${input.WEB_HOSTNAME}

Example: Postal (web + worker + SMTP server), Chatwoot (web + sidekiq).

6. `network_appliance`

Network proxy or ingress. Runs in network_mode: host (needs to see all ports and netfilter), is singleton, and usually has no exposed HTTP route — it is the route.

Aspect Default
Network host (no network isolation)
Healthcheck exit (CLI tools) or external_tcp
Scope singleton
Routes raw TCP/UDP when applicable

Example: Traefik (ingress for the whole DeployAlly stack), SOCKS proxies, VPN gateways.

Relating Archetypes: `needs` and `provides`

Applications consume assets via needs. The block is declarative — it describes what the app needs, with no hardcoded credentials or hosts.

# wordpress.yaml
needs:
  database:
    class: relational
    options: [mariadb, mysql]
    per_instance:
      database: "wp_${instance.uid}"
      user:     "wp_${instance.uid}"
      password: ${secrets.WP_DB_PASSWORD}
      on_provision:    create_user_and_db
      on_decommission: drop_user_and_db
    exports:
      - { from: container.name, to: WORDPRESS_DB_HOST }

On WordPress deploy:

  1. The asset dispatcher looks for a running asset of class: relational on the host (label ccs.systems/asset_class=relational).
  2. Once found, it fires the function declared in on_provision (here, MariaDB's create_user_and_db), passing db_name=wp_<uid>, user=wp_<uid>, password=<secret>.
  3. The asset executes the function via docker exec (creates DB + user + grants).
  4. Asset exports (container.name → WORDPRESS_DB_HOST) become env vars on WordPress.
  5. WordPress comes up and connects to the isolated wp_<uid> database.

On decommission, the reverse cycle: drop_user_and_db is called, removing the DB and user. The shared MariaDB stays up.

Cascade auto-provision

If the asset doesn't exist on the host, the --provision-missing-assets flag instructs the cascade to create it automatically — first option in needs.options, variant marked default: true, same profile as the consumer. There's retry with backoff (3→15s, 5 attempts) waiting for the asset's cold-start.

deployally deploy --species wordpress --instance-uid wp-blog-001 \
  --input WEB_HOSTNAME=blog.example.com \
  --provision-missing-assets \
  --apply

Networks

DeployAlly creates Docker networks automatically based on the archetype:

Archetype Networks
application public + dedicated networks of assets in needs
asset <slug> (dedicated) + public
static public
worker public + dedicated networks of assets in needs
multi_component_saas public + dedicated networks
network_appliance host

Internal service discovery: containers find each other by container name. A WordPress connects to mariadb-shared:3306 via internal DNS on the mariadb network.

Complete Example

Typical stack: Traefik + MariaDB + Redis + WordPress + Adminer.

┌─────────────────────────────────┐
│ Traefik   (network_appliance)   │  host network, singleton
│ ingress + TLS                   │
└────────────────┬────────────────┘
                 │ public
   ┌─────────────┼─────────────┐
   ▼             ▼             ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│WordPress │ │ Adminer  │ │ … apps   │
│ (app)    │ │ (app)    │ │          │
└────┬─────┘ └────┬─────┘ └──────────┘
     │            │
     │ mariadb    │ mariadb
     ▼            ▼
   ┌─────────────────┐    ┌────────────┐
   │ MariaDB (asset) │    │ Redis      │
   │ singleton       │    │ (asset)    │
   └─────────────────┘    └────────────┘

Deploy order implied by the archetype:

  1. network_appliance (Traefik) — singleton, must be up first.
  2. asset (MariaDB, Redis) — singletons that provide functions.
  3. application (WordPress, Adminer) — consume assets via needs.

Next Steps

  • Templates — How each block (image, routes, storage, etc.) varies by archetype.
  • Definitions and Instances — How per_instance provisions isolated resources.
  • Taxonomy — How kingdom/family/species organize the catalog.
By Borlot.com.br on 05/06/2026