Skip to content

YAML Format Specification

The YAML format mimics the Odoo database structure and data, providing a convenient way to export and import data to/from Odoo and store it in version control systems.
Please refer to the YAML Export/Import page for the information on how to export and import YAML files.

Each YAML file contains the following sections:

  • Headercetmix_tower_yaml_version (optional)
  • Manifest — optional metadata for Hangar publishing
  • Recordsrequired; list of Tower records to import

Below is an example of a YAML file which contains a Command record:

# This file is generated with Cetmix Tower.
# Details and documentation: https://cetmix.com/tower
# This is a command example
cetmix_tower_yaml_version: 1
records:
- cetmix_tower_model: command
  access_level: manager
  reference: add_user_to_docker_group
  name: Add user to "docker" group
  action: ssh_command
  allow_parallel_run: false
  note: Adds current user to "docker" group to allow running Docker commands without
    'sudo'
  tag_ids:
  - reference: docker
    name: Docker
    color: 4
  - reference: system
    name: System
    color: 1
  path: false
  code: |-
    {% if tower.server.os == "Debian 11" %}
    /usr/sbin/usermod -a -G docker {{ tower.server.username }}
    {% else %}
    usermod -a -G docker {{ tower.server.username }}
    {% endif %}
  server_status: false

cetmix_tower_yaml_version

Type: Number

This is an optional field, used to identify the version of the YAML format. If the YAML file version is higher than the version supported by Cetmix Tower, an exception will be raised during import.

YAML file version

Current supported version is 1

manifest

YAML field type: Mapping (optional)

Optional metadata block used to publish YAML snippets in the Hangar. The manifest section is not required for import/export; it is mainly used when sharing snippets publicly.

Manifest is NOT a record

The manifest is a top-level mapping (sibling of records), not an item in the records list. Do not use cetmix_tower_model: manifest inside records. The manifest is read only from the top-level manifest: key. Putting it inside records will cause the import to fail.

Manifest for better UX

A complete manifest improves the experience for anyone importing your snippet. Use the description field (multi-line) to include full usage instructions.

Supported keys:

Key Type Description
name String Snippet name
summary String Short summary
description String Full description. Use multi-line for complete usage instructions (see below).
author String or List Author(s) name(s)
version String Version (e.g. 1.0.0)
website String Website URL
license String License identifier. See Available licenses below.
license_text String Custom license text when license is custom
price Number Price (for paid snippets)
currency String Currency code (e.g. EUR, USD)

Manifest description — complete usage instructions

When writing the description field, include a complete usage guide so users know how to configure, use, and troubleshoot the snippet. A well-structured description should cover:

  1. What this snippet does — Clear summary of the purpose (e.g. deploys WordPress with Traefik and Let's Encrypt).
  2. Technology stack — List the software and versions (e.g. Ubuntu 22.04, Docker, Traefik, MariaDB 10.11).
  3. Configuration required before use — Steps the user must complete first (e.g. create a Server in Tower, install the Jet Template on it, configure secrets).
  4. How to use — Step-by-step workflow (e.g. create Jet from template → Run Prepare → Run Build → Run Start → open URL). Mention that the Create Jet wizard can select target state "Running" to auto-run Prepare→Build→Start — one-click deploy.
  5. Troubleshooting — Common issues and how to fix them (e.g. SSL fails → check port 80; DB connection fails → verify secret).

Use a multi-line string (| or >) to keep the description readable.

Available licenses

The license key accepts one of: agpl-3, lgpl-3, mit, or custom. Values like proprietary are invalid — for proprietary licenses, use license: custom with license_text.

Value Description
agpl-3 AGPL-3 (GNU Affero General Public License v3)
lgpl-3 LGPL-3 (GNU Lesser General Public License v3)
mit MIT License
custom Custom license; you must also provide license_text with the full license text.

Example:

manifest:
  name: Docker Setup Commands
  summary: Common Docker installation and configuration
  description: |
    Jet Template that deploys a Docker Compose stack on Ubuntu 22.04.

    Technology stack:
    - Ubuntu 22.04
    - Docker Engine
    - Traefik v2.10
    - MariaDB 10.11
    - WordPress (latest)

    Configuration required before use:
    1. Create a Server in Cetmix Tower (OS: Ubuntu 22.04, SSH configured).
    2. Install this Jet Template on the server.
    3. When creating a Jet, provide: domain_name, db_password, letsencrypt_email.

    How to use:
    1. Create Jet from template.
    2. Run Prepare.
    3. Run Build.
    4. Run Start.
    5. Open https://yourdomain and complete WordPress setup.

    Troubleshooting:
    - If SSL fails: Ensure port 80 is open publicly.
    - If DB connection fails: Check db_password secret.
    - If 502 error: Check WordPress container logs.
  author: Cetmix
  version: 1.0.0
  license: mit
records:
  - cetmix_tower_model: command
    reference: example_command
    ...

Wrong — manifest must not be inside records:

records:
  - cetmix_tower_model: manifest   # Invalid — causes import to fail
    reference: my_manifest
    name: My Snippet

records

YAML field type: List

Required for import

The records key is required for import. If your YAML file does not have a top-level records: key containing a list of records, the importer will fail with "YAML file doesn't contain any records".

Use the Tower format only

Only the Cetmix Tower YAML structure is supported. Do not use alternative schemas with top-level keys such as variables, commands, file_templates, flight_plans, or jet_templates. All entities (commands, plans, variables, file templates, jet templates, etc.) must be defined as items in the records list, each with cetmix_tower_model: <model_name>. See the supported models for valid model names.

A YAML file can contain multiple records, which may belong to different models.

You can organize records in two ways:

  • Nested (single root): Put all records under one root record. Child records (tags, commands, plans, variables, etc.) are defined inline within their parent. This keeps the whole hierarchy in a single tree—common for Jet Templates or Server Templates with many related entities.

  • Flat (one record per list item): Create a separate list item for each record. In this case, each record must appear before any reference to it—define tags, variables, and other dependencies before the records that use them.

    • Keys/secrets: Define keys/secrets before any jet_template, command, or file_template that references them in secret_ids; otherwise the importer will try to create stub keys without the required name field and fail.

    • Jet template dependencies: When a jet_template uses template_requires_ids to reference another jet_template, that required template must either already exist in the database (e.g. imported earlier) or be defined earlier in the same YAML file. Otherwise the importer will try to create a stub jet_template without the required name field and fail.

Record Structure

cetmix_tower_model

YAML field type: String

Odoo model name.
Odoo model names are translated to YAML model names using the following rule: cx.tower.model.namemodel_name.

List of supported models

YAML Model Odoo Model
command cx.tower.command
plan (Flight plan) cx.tower.plan
plan_line cx.tower.plan.line
plan_line_action cx.tower.plan.line.action
variable cx.tower.variable
variable_option cx.tower.variable.option
variable_value cx.tower.variable.value
server cx.tower.server
server_template cx.tower.server.template
server_log cx.tower.server.log
shortcut cx.tower.shortcut
file_template cx.tower.file.template
key cx.tower.key
os cx.tower.os
tag cx.tower.tag
jet_template cx.tower.jet.template
jet_state cx.tower.jet.state
jet_action cx.tower.jet.action
jet_waypoint_template cx.tower.jet.waypoint.template
jet_template_dependency cx.tower.jet.template.dependency
webhook cx.tower.webhook
webhook_authenticator cx.tower.webhook.authenticator
git_project cx.tower.git.project
git_source cx.tower.git.source
git_remote cx.tower.git.remote

This list can be extended in custom modules by inheriting from the cx.tower.yaml.mixin model. Check the Entity Diagram for the model relations.

Manifest is not a model

manifest is not in this list because it is a top-level YAML key, not an importable record. Do not add cetmix_tower_model: manifest to the records list.

Invalid model name

If the model name is invalid, the record will not be imported.

Common mistake: flight_plan

Use plan for Flight Plans, not flight_plan. The YAML model name is plan (the last segment of cx.tower.plan). The conceptual term "Flight plan" refers to this model.

Common mistake: plan line_ids use command_id

Plan lines in line_ids use command_id to reference the command, not command. Each plan line must have command_id (reference or exploded record).

Variable has no default value field

The variable model defines metadata only (reference, name, variable_type, etc.). It does not have default_value_char or any value field. Values are stored in variable_value records, each with variable_id and value_char. Use variable_value_ids on the context: jet_template, server_template, server, or jet. Global values are variable_value records not linked to any entity.

Example:

cetmix_tower_model: command

access_level

YAML field type: String

Access level required for the record (commands, plans, jet states, etc.). Used for permission checks during execution.

YAML value Odoo value Description
user "1" User level
manager "2" Manager level
root "3" Root level

Example:

access_level: manager

reference

YAML field type: String

Unique identifier of the record. If not specified, it will be generated automatically.

Example:

reference: add_user_to_docker_group

Missing reference

If no reference is provided, the record will be created each time the file is imported. This may result in multiple copies of the same record being stored in the database.

Base Odoo Fields

Char

YAML field type: String.

Example:

name: Add user to "docker" group

Text

YAML field type: String.

Example:

  code: |-
    {% if tower.server.os == "Debian 11" %}
    /usr/sbin/usermod -a -G docker {{ tower.server.username }}
    {% else %}
    usermod -a -G docker {{ tower.server.username }}
    {% endif %}

Integer

YAML field type: Number.

Example:

priority: 1

Float

YAML field type: Number.

Example:

percentage: 100.0

Boolean

YAML field type: Boolean.

Example:

active: true

Date

YAML field type: Date.

Example:

date: 2021-01-01

Datetime

YAML field type: Datetime.

Example:

datetime: 2021-01-01 12:00:00

Selection

YAML field type: String.

Odoo Selection field values are represented in the YAML file by selection keys.

For example, the following Odoo field:

use_sudo = fields.Selection(
    string="Use sudo",
    selection=[("n", "Without password"), ("p", "With password")],
    help="Run commands using 'sudo'. Leave empty if 'sudo' is not needed.",
)

Will be represented in the YAML file as:

use_sudo: p  # "p" is the key for the "With password" selection value

Relational Fields

Supported relational fields:

  • Many2many
  • Many2one
  • One2many

Relational fields are represented in YAML in two ways:

Many2one

For Many2one fields, you can use:

  • Reference only: A string with the reference, or a dict with a reference key pointing to an existing or earlier-defined record.
  • Exploded: A full nested record dict (including cetmix_tower_model, reference, and other fields). The related record is created or updated before linking.

Examples:

# Reference only (string)
command_id: very_much_command_test

# Reference only (dict)
command_id:
  reference: very_much_command_test

# Exploded (full nested record)
command_id:
  reference: very_much_command_test
  name: Very much command
  action: ssh_command
  code: Such much code
  variable_ids:
  - cetmix_tower_model: variable
    reference: test_plan_dir
    name: Test Plan Directory

Many2many

For Many2many fields, the value is a list. Each item can be:

  • Reference only: A string with the reference, or a dict with a reference key only (for existing or earlier-defined records).
  • Exploded: A dict with reference and additional fields. The related record is created or updated before linking.

Examples:

# Reference only (plain strings)
tag_ids:
- docker
- system

# Reference only (dicts with reference key)
tag_ids:
- reference: docker
- reference: system

# Exploded (create/update related records)
tag_ids:
- reference: docker
  name: Docker
  color: 4
- reference: system
  name: System
  color: 1

Reference mode

Record is represented as a reference or as a list of references.
Use this mode when:

  • The related record(s) already exist in the database, or
  • The related record(s) were defined earlier in the same YAML file (in another record's exploded/inline section)

You can use a plain reference string or {reference: x} if the same entity was described in the same file before—no need to repeat the full definition.

Exploded mode

Each related record is represented as an entire record or as a list of entire records.
Use this mode when the related record(s) don't exist in the database or need to be updated. See the Many2one and Many2many sections above for examples.

Info

If a reference is not specified, a new record will be created. Once a record is defined in the file (in any record), you can reference it elsewhere in the same file using reference mode.

Unknown keys

Unknown or extra keys in records are ignored during import. Only keys that map to valid Odoo fields are processed.

Import conflict handling

When importing, records are matched by reference. If a record with the same reference already exists in the database, the import wizard lets you choose:

Option Behavior
Skip Do not import the record; keep the existing one unchanged.
Update Update the existing record with values from the YAML file.
Create Create a new record (reference will be auto-generated if needed). Related records referenced in the YAML may also be created instead of linked to existing ones.

The chosen policy applies to all records in the file during that import run.

Jet Template lifecycle in YAML

The Jet lifecycle (Prepare, Build, Start, Stop, Remove, Destroy) is defined via Jet States and Jet Actions, not via fields on the jet template.

Common mistake: plan_prepare_id, plan_build_id, etc.

The cx.tower.jet.template model does not have fields like plan_prepare_id, plan_build_id, plan_start_id, plan_stop_id, or plan_remove_id. These fields do not exist and will be ignored during import.

To define the lifecycle in YAML:

  1. Create jet_state records for each state (e.g. removed, preparing, building, stopped, running, removing).
  2. Create jet_action records for each transition, each with:
  3. state_from_id, state_transit_id, state_to_id
  4. plan_id — the Flight Plan to run when the action is triggered

state_transit_id is required

Every jet_action must have state_transit_id. Transit states (e.g. preparing, building, starting) must be defined as jet_state records and referenced. Import will fail without state_transit_id.

Build and Start need different plans

Start must not reuse the Build plan. Build creates resources (containers, binaries, configs); re-running the same plan on Start fails because resources already exist. Start must use a different plan suited to starting existing resources (e.g. docker start for Docker). Apply the same logic for Stop, Remove, Destroy — each action type needs a plan suited to its purpose. Actions that do the same thing in different contexts (e.g. two Start actions in different template variants, or the same template with different initial states) can share the same plan.

Full lifecycle for production

Production templates should define Stop, Remove, and Destroy actions, not just Prepare/Build/Start. Include at least one action with empty state_from_id for the Create Jet wizard.

Jet template fields for install/uninstall (different from lifecycle):

  • plan_install_id — Installation Flight Plan (when template is installed on a server)
  • plan_uninstall_id — Uninstallation Flight Plan
  • plan_clone_same_server_id — Clone on same server
  • plan_clone_different_server_id — Clone to different server

These are different from the Prepare/Build/Start/Stop actions, which are driven by action_ids (jet_action records). See wordpress-docker-chatgpt.yaml for a complete example.

Entity Diagram

The entity diagram is a visual representation of the YAML file structure.

- command
  - tag              # Tags assigned to command
  - variable         # Variables used by the command
  - os               # Operating systems associated with the command
  - plan             # Child plan executed by the command
  - file_template    # Template used to upload file from the command
- plan
  - plan_line        # Lines of the plan
    - command_id     # Command executed in the plan line (use command_id, NOT command)
    - plan_line_action  # Actions depending on command output
      - variable_value  # Variable values set by the action
  - tag              # Tags assigned to the plan
- server_template
  - variable_value   # Configuration settings
  - shortcut         # Shortcuts to quickly trigger actions
  - tag              # Tags for categorization
  - os               # Default operating system
  - server_log       # Logs associated with the template
  - plan             # Plans executed when server is created/deleted
- file_template
  - tag              # Tags related to the template
  - variable         # Variables used in template
- variable
  - variable_option  # Available options (dropdowns)
  - variable_value   # Assigned values
- variable_value
  - variable         # Inline variables used in variable values
- jet_template
  - jet_state       # Lifecycle states (Draft, Running, Stopped, etc.)
  - jet_action      # Transitions (Prepare, Build, Start, Stop, etc.)
    - plan          # Flight Plan run when action is triggered
  - variable_value  # Template configuration
  - template_requires_ids  # Other jet_templates this one depends on
  - waypoint_template_ids
- key                # SSH key or Secret
- os                 # Operating system
- tag                # Global tagging entity
- git_project
  - git_source       # Sources
    - git_remote     # Source remotes