Generating .neo.yml from docker-compose.yml

neo config generate migrates a compose project automatically — deterministically now, and it tells you exactly what it could not carry over.

6 min read v0.25.2 v0.25.3 v0.25.4

Migrating from compose

neo config generate

Reads docker-compose.yml, identifies the public app service, and writes a .neo.yml with env vars, port, workers and sidecars filled in — a starting draft, not a finished config, and it says so explicitly.

How the app service is chosen

Scoring, highest wins, ties broken by name so the result is identical on every run: a service with build: outranks one without; published ports outrank none; an nginx-proxy style VIRTUAL_HOST/VIRTUAL_PORT counts as a signal when nothing is published. A service whose command is a worker or scheduler (queue:work, schedule:work, horizon, supervisord) is never eligible, regardless of score — it can never become the public app by mistake.

Workers vs. sidecars

A service sharing the app's image or build context, with its own command, becomes a workers: entry — unless its environment conflicts with the app's. Neo workers inherit the app's env verbatim with no per-worker override, so a service that sets DB_QUEUE=study while the app sets DB_QUEUE=nomination can't safely become a worker — mapping it would silently point it at the wrong queue. It stays a sidecar instead, and the output names exactly which variables conflicted.

One-shot services are skipped, not mis-mapped

A service declared restart: "no" — composer install, an asset build, a migrate step — is compose's idiom for "run once and exit." Mapped to a worker it would loop forever; as a sidecar it would exit and look broken. Generate skips these and says where the work belongs instead:

⚠ composer → skipped: runs once and exits — move it to hooks.pre_build
⚠ setup    → skipped: runs once and exits — move it to release:

Nothing is dropped without a word

Every compose key the typed parser doesn't act on — healthcheck:, deploy.replicas, Traefik labels: — is listed under "Not carried over," with a pointer to the Neo equivalent when one exists:

Not carried over — review these by hand
~  app     healthcheck:   add health: to .neo.yml
~  app     deploy:        replicas map to scale: in .neo.yml

What it can't do

neo deploy builds from a Dockerfile; a compose file with no build: anywhere describes prebuilt images only, and generate says so rather than producing an undeployable config silently. And Neo deploys one public app per .neo.yml — a compose file with several VIRTUAL_HOST-bearing services describes several sites, and generate names which one it picked and that the others need their own project.

Try it yourself — Neo is free, with every feature unlocked.

Get started free