Skip to content
NebulaCtrldocs
Guides

Import a Docker Compose file

Review a repository's Compose files in four steps and stage their services, databases, secrets and wiring in one change set.

A repository that describes its services in a Docker Compose file can move to NebulaCtrl in one pass. The import proposes one service per Compose service, replaces database containers with database services, and rewrites hostnames, connection strings and shared passwords to ${{ }} references. You review the proposal and stage it as one change set. Nothing is created before you stage, and nothing deploys until you apply the change set. What each Compose key becomes is listed in Compose mapping.

An import is a one-time move. It does not keep the Compose file and the project in sync, and it writes no nebula.toml. To keep a project's services in Git, see Describe a project in Git.

Before you begin

  • A Git connection that can read the repository. See Git providers and registries.
  • The Compose files are in the repository root or one folder below it, and they use no include.
  • To stage, the environment needs a bound cluster. Without one, the last step stays disabled and says so.
  • To import into an existing project, you need the deployer role or higher. To create a project, you need the admin role.

Start the review

  1. Select New project, enter a Name, and under Start with choose From a repository.
  2. Pick the Git connection, if you have more than one, and the repository.
  3. Under Set up the project with, choose the entry that starts with Import and names the Compose file, such as Import docker-compose.dev.yml. It is listed when the repository's default branch has Compose files. If the repository also has a nebula.toml, Use nebula.toml is selected first, so pick the import yourself.
  4. Read the card under the choice. It names the file and the number of services, and shows what each would become. With more than one Compose file, it lists the files selected by default, and you choose among them in the review.
  5. Select Create and review N services. The button reads Create and review the services when no service can be imported.

NebulaCtrl creates the project and opens the review over the new canvas. If the first non-production environment has no cluster, NebulaCtrl binds it to the first connected cluster. It uses the repository's default branch.

Choose the files and services

The review has up to four steps, with Back and Next. What you typed is kept when you go back. Next stays disabled until the step is settled, and the footer names the first thing it waits for. Show me in the footer jumps to it.

  1. Services.
    • If the repository has more than one Compose file, tick the files to import. Each file is read on its own and never merged with another.
    • For each service, tick it, change its name if you want, and read what it becomes. A database image has a Managed or Image toggle.
    • Settle every decision that a service shows. Select Accept, or enter the value the decision asks for. A service marked blocked cannot be ticked.
  2. Secrets and values. Under You need to provide, type a value for each field, or tick I'll set it later. Generated for you lists the secrets NebulaCtrl draws once; turn Generate a random value off to type your own instead. From your compose file lists the values copied from the file, and Linked lists the values wired to another service. The step is skipped when no ticked service has a variable and the file has no shared value to review.
  3. Wiring and notes. A read-only table of every rewritten variable with the value it replaced, and the warnings and notes about the file. The step is skipped when nothing was rewritten and there are no notes.
  4. Stage. A summary of what the import creates, and the variables you chose to set later.

A service is not ticked by default when it is behind a Compose profile, is a one-shot that nothing folds in, is scaled to zero, or would pass the limit of 20 services in one import.

Settle a decision

A decision is something only you can answer. The common ones:

  • Some variable names can't be stored. NebulaCtrl stores uppercase names. Set the listed names another way, such as a config file in the image, then select Accept to import without them.
  • A volume shared between services. Select Keep the volume here on the one service that keeps it. The others get none.
  • The release step has no command. A one-shot service that others wait for became a release command, but it has no command of its own. Enter the command, or select Accept without a release command.
  • A service would run as root, or runs as a named user. An image that starts as root, or as a named user that Kubernetes cannot verify, needs a user ID. Enter one in Run as uid, or select Allow root for the service. An image that starts under an init system allows only root.

Every decision, with the ids you accept over the API, is in Compose mapping.

Stage the import

On the last step, select Stage N services in ENVIRONMENT. NebulaCtrl re-reads the selected files at the commit the proposal was made from, so a push in between changes nothing. It then stages, in the environment's change set, the services of every selected file, their variables, files and release commands, and their first deploys, databases first. All of it is written together or not at all. A file you selected that cannot be loaded stops the import instead of importing less, so untick it or fix it first.

NebulaCtrl runs containers as non-root unless you allow root for a service. A git service's first deploy builds the branch you picked.

Verify

You see Staged N services from compose.yaml — review and commit below, and the project canvas shows the services marked as staged. If variables were left empty, a message lists them as Still empty: web: OIDC_CLIENT_SECRET, with a Set values button that opens the service's Variables tab. An empty variable shows an empty tag there, and a deploy never waits for it. A service that reads one fails at startup until you set it.

To deploy, select Deploy N changes in the staged bar, or Request approval on a protected environment, and apply the change set. See Deploy order for what waits for what.

Import over the API

Preview first, then import with the same choices. The preview answers with a proposal and never writes anything.

curl -X POST https://CONTROL_PLANE/api/v1/repository-config/detect-compose \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "gitConnectionId": "CONNECTION_ID",
    "repository": "acme/shop",
    "branch": "main",
    "projectId": "PROJECT_ID",
    "choices": {
      "files": ["compose.yaml", "worker/compose.yaml"],
      "services": { "compose.yaml#cache": { "include": false } }
    }
  }'

CONTROL_PLANE, TOKEN, CONNECTION_ID and PROJECT_ID are your control plane's address, an API token, the Git connection's ID and the project's ID. For a public repository, send gitUrl instead of gitConnectionId and repository. Send context to search a folder other than the root.

The answer is {"status": "found" | "absent" | "error", "message"?, "proposal"?}. A repository or file that cannot be read is status error with a message, not an HTTP error. The proposal lists every file found with its selected flag and status (ok, error or unread), each service under its key <file>#<composeName>, each variable with its origin and needsInput, and each service's decisions.

Import with POST /api/v1/projects/{id}/compose-import (importCompose). Send the same source and choices, plus commitSha from the proposal and the environmentId to stage into.

curl -X POST https://CONTROL_PLANE/api/v1/projects/PROJECT_ID/compose-import \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "gitConnectionId": "CONNECTION_ID",
    "repository": "acme/shop",
    "branch": "main",
    "commitSha": "COMMIT_SHA",
    "environmentId": "ENVIRONMENT_ID",
    "choices": {
      "files": ["compose.yaml"],
      "services": {
        "compose.yaml#app": {
          "values": { "OIDC_CLIENT_SECRET": "VALUE" },
          "accept": ["variable-names"]
        }
      },
      "acceptEmpty": ["compose.yaml#worker#SMTP_PASSWORD"]
    }
  }'

The import answers 201 with the services created, the environment's changeSet, and needsInput: the variables, as {serviceId, service, key}, that were accepted empty and still need a value. It answers 422 when a decision or a value is missing. The message names every included variable that needs input and has neither a value nor an acceptEmpty entry, and every decision not accepted. A 409 means a slug is already used in the project. The choices are listed in Compose mapping.

A value you send is sealed when the service is imported. It is never sent back and never logged.

Next steps

On this page