Skip to Content
DocumentationTutorialsPublish your first app

Publish your first app

Take a Docker image and turn it into an entry in the Companion App Store that anyone can install in one click.

Time: ~45 minutes · You’ll need: a published Docker image, and a GitHub account

Companion Hub App Store, Featured view: a search box and category list on the left (All, Expose a port, Alternatives, Featured, Agents, AI, and more), a Companion Intelligence row with Companion Earth, Companion Memory, Companion Planning, and Companion Scan, and a Featured row with Audiobookshelf, Code Server, Collabora Online, and ComfyUICompanion Hub App Store, Featured view: a search box and category list on the left (All, Expose a port, Alternatives, Featured, Agents, AI, and more), a Companion Intelligence row with Companion Earth, Companion Memory, Companion Planning, and Companion Scan, and a Featured row with Audiobookshelf, Code Server, Collabora Online, and ComfyUI
Where you are headed: an entry in the Companion App Store, installable from a Hub in one click.

The path from a directory to a store entry, including the draft-listing route that Testing before you submit describes:

What you’ll have at the end

  • An app directory that satisfies every publication requirement
  • A submission for review — through the web form , cihub submit, or a pull request against CI-Marketplace

The four required files

Every app is a directory named for its slug. In the CI-Marketplace repository it lives under apps/<slug>/. All four files are required — missing or empty ones cause the app to be skipped at deploy and flagged by the audit tooling.

    • config.json
    • docker-compose.json
      • description.md
      • logo.png

Start from the template

If you have access to the CI-Marketplace repository, clone it and copy the template:

cp -r apps/_template apps/my-app

Otherwise, create a folder named my-app with the four files below. The web form and cihub submit accept the folder from anywhere on your machine.

The slug you choose is your app’s identity — the directory name and the id in config.json must match.

Write config.json

Ten fields are required:

FieldTypeNotes
idstringMust match the directory name
namestringHuman-readable name
versionstringSemver recommended
short_descstringOne line, 500 characters or fewer
authorstringYou, or the upstream maintainer
portnumberPrimary port; optional for no_gui and mcp entries
sourcestringURL to the source repository
categoriesstring[]e.g. ["utilities"]
availablebooleantrue → published and visible; false → ingested as a hidden draft
supported_architecturesstring[]e.g. ["arm64", "amd64"]

A minimal valid file:

{ "id": "my-app", "name": "My App", "version": "1.0.0", "short_desc": "A short description of what the app does.", "author": "your-name", "port": 8080, "source": "https://github.com/you/my-app", "categories": ["utilities"], "available": true, "supported_architectures": ["arm64", "amd64"] }

Set available: false while you’re still iterating. Your app is ingested as a draft and stays hidden from the store until you flip it.

Optional fields worth knowing: description, website, exposable, dynamic_config, form_fields, cihub_app_version, force_pull, no_gui, and mcp. See the config.json reference.

Write docker-compose.json

JSON, not YAML, on schema version 2. Exactly one service must be marked isMain.

{ "schemaVersion": 2, "services": [ { "name": "my-app", "image": "org/my-app:1.0.0", "internalPort": 8080, "isMain": true, "volumes": [ { "hostPath": "${APP_DATA_DIR}/data", "containerPath": "/data" } ], "environment": [{ "key": "KEY", "value": "value" }] } ] }

environment is a list of key and value pairs, not an object, and every value needs at least one character. Common per-service properties: name, image, internalPort, isMain, volumes, environment, dependsOn, entrypoint, extraLabels, plus ports, command, healthCheck, and more. See Dynamic Compose.

Pin a real image tag rather than latest where you can. Users get whatever the tag resolves to at install time.

Add metadata

  • metadata/description.md — the long description on your app’s detail page
  • metadata/logo.png or logo.jpg — at least one, and it must not be 0 bytes

Test on a device

Install your app on a Hub and confirm it starts, serves on its port, and persists data across a restart.

This step is harder than the marketplace README implies. See Testing before you submit below — the documented local route is currently disabled in the dashboard.

Capture a verification screenshot

Save a screenshot of your app running to:

e2e/screenshots/my-app.png

This is the QA record, in the CI-Marketplace repository, that the app was installed and verified on a device. It is strongly recommended for the pull-request route and surfaced by the audit tooling, but it is not currently a hard deploy gate — the pipeline ships any apps/<slug>/ with a valid config.json. Store visibility is controlled by available, not by the screenshot.

Submit it

Pick a route. All three need the same four files and end in review — see Submitting apps to the marketplace for the details.

  • Web form (recommended). Go to ci.computer/store-submit  and submit your app there. You don’t need repository access.
  • CLI. Run cihub login, check the folder with cihub submit --dry-run ./my-app, then cihub submit ./my-app. It ingests your app as pending; it never publishes on its own.
  • Pull request. If you have access to CI-Marketplace, commit your directory and open a PR against it. The pipeline deploys on merge.

Testing before you submit

The marketplace instructions say to install your app on a device before submitting. A custom app store could point Hub at your fork, but that route is not available from the dashboard.

Hub’s Settings → App Stores → Add App Store dialog is disabled: it shows “Feature coming soon” above the detail text “Custom app store setup is under maintenance. Your Hub uses the CI Marketplace automatically; no manual store URL is needed.” The cihub CLI has no app-store command either. See Custom app stores for what the App Stores page can still do.

So test from a draft listing instead: submit your app with available: false. It deploys but stays out of the public store, and you can install it on your own Hub and verify it there. Flip available to true in a follow-up PR once it works.

Why not call the API directly? The Hub backend does still serve an app-store creation endpoint, so the capability was not deleted from the server. It is not a supported path — nothing in the UI manages a store created that way, and the request shape is not guaranteed — so it is not documented as a workaround here.

Whether the dialog returns is genuinely unsettled: the dialog text says “coming soon”, while the comment in the source that disabled it describes manual store URLs as a removed pre-Companion-Hub feature. The supported path remains unsettled.

Next

Last updated on