Skip to main content
Closed beta. Modules extend ankra migrate, which is gated by the backups feature, in closed beta. The workflow is stable but the surface may still change, and the feature is enabled per organisation on request. ankra migrate modules, detect, convert and export read local files, need no login, and run whether or not the feature is on for you; ankra migrate up and ankra migrate restore, which carry the data into a cluster, need it enabled. Contact support to have it turned on for your organisation.
A module teaches ankra migrate one source format. It is an executable named ankra-module-<name> that answers three verbs, each with a JSON request on stdin and a JSON reply on stdout, so it can be written in any language. The built-in docker module is the same contract implemented in-process, and is the reference implementation.

When to write one

ankra migrate convert turns a deployment description into an ImportCluster manifest plus the manifests its stack refers to. The docker module reads Compose files, bare Dockerfiles and the running Docker daemon, and is the only module that ships with the CLI. Every other source format is a module somebody writes: a Heroku-style Procfile, a Nomad job specification, a systemd unit directory, or the in-house deployment description your team has been maintaining for years. Write one when you convert the same format more than once. A module makes the translation repeatable and reviewable: the same input produces the same stack, in a pull request, for every service that uses that format.

How the CLI finds your module

The executable is named ankra-module-<name>, where <name> is the token users type after --module. The CLI looks in ~/.ankra/modules first and then in every directory on PATH, so a module can be installed without touching PATH at all. It must be executable, and a name that collides with a built-in is ignored, so no module can shadow docker.
ankra migrate modules runs the describe verb of every candidate it finds and lists the name, version, source (built-in, or the path it was found at) and summary. A module that fails to load is reported on stderr as skipped <path>: <reason> and the listing carries on, so one broken executable never takes the command down. Pass -o json to see the full description, including the capabilities the table does not show. ankra migrate convert, export and up choose a module by asking every one of them whether it recognises the directory and taking the most confident answer. Ties are broken alphabetically by module name, and a directory nothing recognises is refused with no module recognises <dir>. --module <name> skips detection and picks one explicitly.

The protocol

One verb per invocation, as argv[1]. The request is JSON on stdin, the reply is JSON on stdout. Exiting non-zero fails the verb, and the module’s stderr becomes the reason the user sees (the last five lines of it). The protocol version is 1; the CLI refuses a module that reports any other, rather than guessing at the shape of its output. export is optional and answered only by a module that lists "export" under capabilities in its description. The CLI trusts the capability, so a module that omits it is never asked.

describe

No input. Report what the module is:
version is the module’s own version, not the CLI’s. file_patterns is shown to users so an empty detection is explainable rather than mysterious. describe must not touch the filesystem: it is called for every listing.

detect

The request is {"dir": "/absolute/path"}. Reply with a confidence from 0 (not mine) to 1 (certain), the files found, and a one-line reason - including for a zero score, because “not mine, and here is why” is an answer rather than a failure:
Reserve 1 for an unambiguous marker file. A module working from a heuristic should score itself lower so a more specific module wins the directory: the built-in docker module scores a Compose file 1 and a lone Dockerfile 0.6 for exactly that reason.

convert

The request carries the directory, the names the output will use, and every --option key=value the user passed, untouched, so a module can take input the CLI knows nothing about:
The reply is the resources:
  • cluster is the ImportCluster the CLI writes to cluster.yaml. Every from_file in its stacks must name a key of files, and it must have a name; the CLI validates both before it writes anything.
  • files keys are paths relative to the output directory. An absolute path, or one containing .., is rejected before a byte is written.
  • warnings are for everything the module could not translate faithfully: an unmappable construct, a value the user has to supply, a credential written in plain text. A partial conversion a person can finish beats none, so warn rather than fail wherever you can.
Four rules keep the output reviewable:
  • Never write to the source directory. Convert reads; the CLI writes.
  • Be deterministic. Sort everything. Output that reorders itself on each run cannot be reviewed in a pull request.
  • Keep secrets out of ConfigMaps. Put credentials in a Secret and warn that it needs encrypting with ankra cluster encrypt before it is committed.
  • Encode dependency order as parents. A workload that must start after another lists it as parents: [{name, kind: manifest}]. That is how a source format’s start order survives the conversion.
Set enableServiceLinks: false on the pods you generate. Kubernetes injects a <SERVICE>_PORT variable for every Service in the namespace, and applications that read a variable of the same name break on it.

export, for a module that can dump its data

A module that can also dump the databases behind its source lists "capabilities": ["export"] in describe and answers a fourth verb, which backs ankra migrate export. The request adds the output directory, which the CLI has already created:
Write each dump under output_dir and reply with what you wrote and where it restores to - the Service and Secret that convert generated for the same workload, so the restore needs nothing the user has to look up:
  • engine is postgres or mysql, and nothing else can be restored. format is pg_custom (a pg_dump -Fc archive, PostgreSQL only) or sql (plain SQL, including pg_dumpall --globals-only output and mysqldump files). kind is database, which must name its database, or globals, which only PostgreSQL has.
  • target.host must be a Service in target.namespace. The platform accepts db, db.shop, db.shop.svc and db.shop.svc.cluster.local, and refuses anything else with Database db target host "..." is not a Service in namespace shop - the restore Job carries the target’s credentials, and a host the manifest could point anywhere would carry them out of the cluster.
  • Artifact paths are relative to output_dir and may not escape it. manifest.json and SHA256SUMS are refused as artifact names, because the CLI writes them itself: it measures every file’s size and SHA-256 sum, rejects one that is empty or was never written, and finalises the export into a self-contained directory that sha256sum -c can verify. Do not report sizes or checksums yourself.
  • Narrate progress on stderr. It is relayed to the user live while the verb runs, which matters when a dump takes minutes, and it is shown as the reason on failure.
  • A single artifact above 625 GiB cannot be uploaded, so split a database that large across smaller dumps.

A worked example

examples/modules/ankra-module-procfile in the CLI repository is a complete module in about a hundred lines of Python. It reads a Heroku-style Procfile - one name: command per line - and emits a Deployment per process type, plus a Service for web. The shape of it is the whole contract:
A Procfile says how to run a process, never what to run it from, so its convert exits 2 with the procfile module needs --option image=<registry/repo:tag> on stderr when that option is missing. That is the pattern for input the CLI knows nothing about. Install it and run it end to end:

Testing a module

The verbs are ordinary processes, so test them without the CLI in the way:
Then check how the CLI sees it. ankra migrate detect <dir> prints every module’s confidence, files and reason, most confident first, which is exactly what convert picks from. ankra migrate convert --dry-run prints the rendered cluster.yaml and the files that would be written without writing any of them, and still runs the full validation, so an unsafe path or a manifest pointing at a file you did not return fails there rather than on disk.

What an external module does not get

Two capabilities of the built-in docker module are Go interfaces rather than protocol verbs, so an external module cannot implement them today.
  • No plan preflight in ankra migrate up. The built-in module can describe an export before running it, which is how the plan reports each database’s size against your free disk. An external module is exported without that preflight, and the plan says so: the <name> module does not describe its export up front; sizes are unknown until the dump runs.
  • No --stop-source. Stopping the source’s non-database services before the final dump is the built-in module’s, so ankra migrate up --stop-source is refused for an external module with the <name> module cannot stop the source's services; stop them yourself, then run without --stop-source.
  • Restores are PostgreSQL and MySQL only, in the sql and pg_custom formats, into a Service in the target namespace. A module can dump anything it likes, but the platform will only restore that.

Next steps

Move a Docker deployment

What the built-in module does, end to end.

Stacks

The shape your cluster reply has to produce.

Import a cluster

The ImportCluster manifest a conversion writes.

Ankra CLI

Install the CLI and browse the rest of its commands.