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
What you’ll have at the end
- An app directory that satisfies every publication requirement
- A pull request against CI-Marketplace
The four required files
Every app is a directory 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
Fork and clone CI-Marketplace , then:
cp -r apps/_template apps/my-appThe 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:
| Field | Type | Notes |
|---|---|---|
id | string | Must match the directory name |
name | string | Human-readable name |
version | string | Semver recommended |
short_desc | string | One line, 500 characters or fewer |
author | string | You, or the upstream maintainer |
port | number | Primary port; optional for no_gui and mcp entries |
source | string | URL to the source repository |
categories | string[] | e.g. ["utilities"] |
available | boolean | true → published and visible; false → ingested as a hidden draft |
supported_architectures | string[] | 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": "value" }
}
]
}Per-service properties: name, image, internalPort, isMain, volumes, environment, dependsOn, entrypoint, extraLabels. 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 pagemetadata/logo.pngorlogo.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.pngThis is the QA record that the app was installed and verified on a device. It is strongly recommended 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.
Open a pull request
Commit your directory and open a PR against CI-Marketplace. The pipeline deploys on merge.
Testing before you submit
The marketplace instructions say to install your app on a device before submitting, and other pages here have described pointing Hub at a fork via a custom app store. That route is not currently available from the dashboard.
Hub’s Settings → App Stores → Add App Store dialog is disabled. It shows “Feature coming soon” and the detail text “Custom app store setup is under maintenance. Your Hub uses the CI Marketplace automatically; no manual store URL is needed.” The source comment is explicit: manual app store URLs were a Tipi-era feature and were removed.
The backend still exposes an app-store creation endpoint, so the capability has not been deleted outright — but the supported user-facing path is gone.
Open questions for whoever owns the marketplace flow. These are why this section describes the situation instead of prescribing a workaround:
- How should a contributor test an app before submitting? Is there a
supported route today — a debug endpoint, a CLI command, a dev-only flag —
or is the honest answer “submit as
available: falseand test from the draft”? - Is the create-app-store API still supported, or is it retained only for internal seeding? If it works, the request shape should be documented here. If it doesn’t, Create your own app store and Custom App Stores both describe a removed feature and should be retired.
- Is the custom-store UI coming back? The dialog says “coming soon”, which reads as temporary; the code comment reads as permanent. Those disagree.
Until they’re settled, the reliable path is submitting with available: false
and testing the draft.
Next
- Submitting apps to the marketplace — the reference, including AI-enabled apps
- config.json reference
- Dynamic Compose