---
title: "Deploy a custom (non-catalog) app"
method: POST
path: "/platform/deployments/custom"
tags: ["Platform"]
---

# Deploy a custom (non-catalog) app

`POST /platform/deployments/custom`

## Request

- Base URL: `https://api.imprezahost.com/v1`
- URL: `https://api.imprezahost.com/v1/platform/deployments/custom`
- Auth: API key in header `X-API-Key` and API key in header `X-API-Secret`

Creates a custom deployment. **Source mode is auto-detected**
from which body fields are populated — pick exactly one:

- **Image** — set `image` to a public registry reference. Agent
  does `docker pull` + `docker run`.

    ```json
    { "name": "my-nginx", "agent_id": "agt_...", "image": "nginx:alpine",
      "target_port": 80, "domain": "static.example.com" }
    ```

- **Dockerfile via tarball** — first
  `POST /platform/deployments/custom/contexts` with a gzip
  tarball of your project, then reference the returned
  `context_id` here.

    ```json
    { "name": "my-app", "agent_id": "agt_...", "context_id": "ctx_...",
      "dockerfile_path": "Dockerfile", "target_port": 8080,
      "cpus": 1.0, "memory_mb": 512, "domain": "myapp.example.com" }
    ```

- **Dockerfile via git URL** — point at a git repo (public or
  private); the agent shallow-clones at deploy time. Private repos
  set `git_auth_method`: `deploy_key` (SSH URL + a generated deploy
  key whose public half is returned for you to add to the repo —
  works with GitHub, GitLab, Bitbucket, Gitea or self-hosted) or
  `pat` (https URL + a token in `git_pat` — GitHub or GitLab; for
  Bitbucket use `deploy_key`). Public repos on any host need no
  auth (`git_auth_method` defaults to `none`).

    ```json
    { "name": "my-app", "agent_id": "agt_...",
      "git_url": "https://github.com/you/your-repo", "git_ref": "main",
      "dockerfile_path": "Dockerfile", "target_port": 8080,
      "domain": "myapp.example.com" }
    ```

    ```json
    { "name": "my-app", "agent_id": "agt_...",
      "git_url": "git@github.com:you/private-repo.git", "git_ref": "main",
      "git_auth_method": "deploy_key", "target_port": 8080 }
    ```

- **Manifest** — full docker-compose YAML, treated like a
  catalog deploy. `cpus`/`memory_mb` are ignored in this mode —
  use compose's `cpus:` / `mem_limit:` per service.

    ```json
    { "name": "my-app", "agent_id": "agt_...",
      "manifest": { "runtime": { "type": "docker-compose", "compose_yaml": "..." } },
      "domain": "myapp.example.com" }
    ```

    **Routing contract** (manifest mode only): when your routes
    use the standard `{deployment_id}-<suffix>:<port>` upstream
    pattern, the matching service in `compose_yaml` MUST
    declare `container_name: ${DEPLOYMENT_ID}-<suffix>`.
    Without it, Docker Compose auto-generates names like
    `app-1` that the reverse proxy can't resolve, and every
    HTTPS request returns 502. The server rejects manifests
    that violate this contract with `400 INVALID_REQUEST` at
    create time. See the `llms-full.txt` doc for full examples.

- **Compose import** — review `compose_yaml` with
  `POST /platform/deployments/custom/compose/prepare`, explicitly select
  `web_service` and `target_port`, then send the returned `analysis_id`
  as `compose_review_id` alongside the same YAML and selection.
  The supported public-image subset becomes a frozen manifest; service
  names, local volumes and HTTP routing are managed by the platform.
  See https://docs.imprezahost.com/compose-import.html for limits.

`name` is per-account unique (3–100 chars matching
`^[a-z0-9][a-z0-9_-]{1,98}[a-z0-9]$`).

For deploys created with `git_url`, see
`/platform/deployments/custom/{id}/git-webhook/*` to wire up
auto-redeploy on push.

## Request body

- object
  - `name` string, required: Per-account unique name. 3–100 chars `[a-z0-9_-]`.
  - `agent_id` string, required
  - `domain` string: Public hostname (FQDN, no scheme). Optional — leave empty for an auto-generated `<name-slug>-<6hex>.imprezaapps.com` subdomain (the server creates the Cloudflare record). Pass a `*.imprezaapps.com` name and the server still ensures the CF record. For customer-owned apex domains, point the A record at the agent IP before calling. Required only when `onion=false` AND your operator hasn't configured an imprezaapps apex. **Uniqueness:** `*.imprezaapps.com` is a shared namespace — first-come-first-served across all accounts. Picking a name already claimed by any live deployment returns `400 INVALID_REQUEST` with "already in use". Names are released when the deploy fully uninstalls.
  - `onion` boolean: Publish a Tor v3 hidden service. Set `domain=""` for onion-only.
  - `cpus` number, float: Cgroup CPU ceiling. Ignored in `manifest` mode.
  - `memory_mb` integer: Cgroup memory ceiling in MB. Ignored in `manifest` mode.
  - `target_port` integer: Container port the reverse proxy forwards to.
  - `vars` object: Extra env vars to drop into the container's `.env`.
  - `image` string: Image mode: public registry reference like `ghcr.io/user/app:tag` or `nginx:alpine`.
  - `context_id` string: Dockerfile-tarball mode: id returned by `POST /platform/deployments/custom/contexts`. Also selects a retained upload for Compose local builds and auxiliary files.
  - `git_url` string: Dockerfile-git mode: an https URL (public on any host, or private with git_auth_method=pat on GitHub/GitLab) or an SSH URL like git@host:owner/repo.git (private, git_auth_method=deploy_key — any host with SSH deploy keys).
  - `git_auth_method` 'none' | 'deploy_key' | 'pat': Auth for a private git repo. `none` = public (default; any host). `deploy_key` = SSH; the platform generates an ed25519 keypair and returns the public half in `git_auth.public_key` for you to add as a read-only deploy/access key — works with any host that supports SSH deploy keys (GitHub, GitLab, Bitbucket, Gitea, self-hosted). `pat` = HTTPS using the token in `git_pat`, for GitHub or GitLab; for Bitbucket use `deploy_key`.
  - `git_pat` string: Repo-scoped read token — a GitHub fine-grained/classic PAT (Contents: Read) or a GitLab personal/project access token (read_repository). Required when git_auth_method=pat; not used for Bitbucket (use deploy_key instead). Encrypted at rest; never returned.
  - `git_ref` string: Branch / tag / commit to clone in git mode.
  - `build_strategy` 'dockerfile' | 'node_npm' | 'node_npm_static' | 'python_pip' | 'php_composer' | 'static_files' | 'go_build': static_files serves visible files from project_dir with index.html and no build step. go_build compiles a static binary using pinned Go 1.25 and read-only modules. Both default to port 8080, non-root runtime and built-in health probes. php_composer uses PHP 8.4/Apache, Composer 2, matching composer.json/composer.lock and php_document_root (default public with index.php). Non-root runtime, default port 8080 (1024-65535). Production HTTPS archive dependencies; no scripts/plugins, private credentials, custom repositories, frontend build, migrations or .htaccess overrides. python_pip uses Python 3.13, flat PyPI requirements.txt and explicit start_command, non-root execution and port 8000 by default. No options/includes/URLs/local projects, system packages or private build credentials. node_npm_static builds the selected output folder (dist default) and serves static files with non-root Nginx and SPA fallback (build script required, no start; port 8080 default). No SSR; runtime vars do not alter browser bundles. node_npm generates a Node 24 npm server recipe using Compose 2.17+ and BuildKit. The selected project folder must contain package.json, package-lock.json and a production start script; no workspaces, private npm config or build secrets. Uses the supplied HTTP port (3000 if omitted), npm ci, optional build, production prune and npm start as non-root. Only Git or context sources.
  - `go_package` string: go_build main package folder; default dot. No traversal or workspaces.
  - `php_document_root` string: php_composer only. Public subfolder within project_dir with index.php; up to five non-hidden path segments, no parents/vendor/node_modules or symlinks. Saved at creation and inherited by previews/redeploys.
  - `start_command` string: Required only for python_pip. Single-line production shell command, up to 1000 UTF-8 bytes, without control characters or credentials. PORT and HOST are runtime variables. Saved at creation and inherited by previews/redeploys.
  - `tor_egress` boolean: Opt-in custom image/source runtime through Tor SOCKS5h. Requires agent 0.6.20+, local MCP 0.42.0+, tor-egress-v1 and Docker 28+. The application must use the Tor SOCKS5h proxy; existing servers require an explicit agent update. No direct fallback; builds/source retrieval are not anonymized. Imported manifests, catalog installs and external service bindings are unsupported.
  - `require_healthy_start` boolean: node_npm, python_pip or php_composer only. Require an explicit healthcheck_path to become healthy before deployment succeeds, including the first install. Requires agent 0.6.3+. Failed first installs remove containers and preserve volumes; eligible previous releases recover. Omit or false keeps legacy behavior. Saved at creation and inherited by previews/redeploys.
  - `startup_timeout_seconds` integer: Only with require_healthy_start=true. Startup observation budget, default 60 seconds, separate from builds and recovery.
  - `healthcheck_path` string: node_npm, python_pip or php_composer only. Optional HTTP path such as /health (200 ASCII characters max). Checks 127.0.0.1 on target_port and requires 2xx without redirects. Omit for /: Python/PHP require 2xx; legacy Node accepts status below 500. Saved at creation; previews and redeploys retain it. A failed replacement recovers a verified healthy previous release; first installs keep the agent startup warning policy.
  - `public_build_vars` object: Generated npm recipes only. Public values for npm run build, never secrets. Keys use VITE_, NEXT_PUBLIC_ or PUBLIC_ followed by uppercase letters/digits/underscores (128 characters max). Max 4096 UTF-8 bytes per value and 16384 bytes for names plus values. No NUL. Values may appear in bundles/image metadata/logs. Not passed to npm ci or automatically to runtime. Saved at creation and inherited by previews/redeploys; changing values requires a new deployment.
  - `build_secrets` object: Dockerfile/npm build-only credentials, up to 256 KiB total. Requires agent 0.6.11+; local MCP inputs require 0.34.0+. Requires agent build-secrets-v1. Values encrypted, not returned. npmrc installs dependencies; other names mount during build. Trusted source only; build output withheld.
  - `python_package_manager` 'pip' | 'uv@0.12.15': python_pip with pyproject.toml and uv.lock, locked production sync and public PyPI only. Requires agent 0.6.11+; local MCP inputs require 0.34.0+. Omit for pip. No uv workspaces or custom indexes.
  - `node_package_manager` string: Exact standalone pnpm/Yarn pin matching package.json and its lockfile, for node_npm/node_npm_static only. Requires agent 0.6.11+; local MCP inputs require 0.34.0+. Omit for npm. Cannot combine with npm_workspace. Immutable installation; source Yarn configuration/plugins excluded.
  - `npm_workspace` string: Declared npm workspace relative to project_dir, which retains the root lockfile and sibling packages. npm strategies only. Builds in declaration order; selected app starts. Static output is relative to the workspace. Local MCP inputs require 0.34.0+.
  - `project_dir` string: Generated npm/Python/PHP strategies only. Independent project folder relative to the Git/upload root, with its own package.json and lockfile, requirements.txt for Python, or composer.json/composer.lock for PHP. Dot selects the root. Up to five non-hidden segments; no parent/node_modules segments or symlink components. Saved at creation and inherited by previews; redeploy retains it. Shared workspaces and dependencies outside this folder require a custom Dockerfile.
  - `static_output_dir` string: node_npm_static only. Output folder relative to project_dir, containing index.html. Up to five non-hidden segments; no parent/node_modules segments or symlinks. Saved at creation and inherited by previews.
  - `static_spa` boolean: node_npm_static only. True serves index.html for unknown routes; false returns 404. Saved in the recipe; redeploy retains it.
  - `dockerfile_path` string: Path to the Dockerfile inside the context / repo root; ignored for generated npm recipes.
  - `compose_yaml` string: Compose import source, mutually exclusive with image/Git/context/manifest. Public images and self-contained stacks only; review first. Stored as a frozen manifest for redeploy. Limit is 64 KiB UTF-8.
  - `web_service` string: Compose import: explicitly selected HTTP service. target_port must be an integer from 1 to 65535.
  - `compose_review_id` string: Compose import: analysis_id from reviewing the exact YAML, web_service and target_port. Runtime variables and server access are checked at creation.
  - `manifest` object: Manifest mode: full docker-compose manifest object with `runtime.type: docker-compose` + `runtime.compose_yaml`.

## Response `201`

Custom deployment enqueued

## Other responses

- `400`: Validation failed on the request body or query parameters
- `404`: Resource not found
- `409`: Request would violate a unique constraint or state precondition

---

[API](https://skmtc.dev/imprezahost/apis/impreza-host-public-api.md) · [All operations](https://skmtc.dev/imprezahost/apis/impreza-host-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/imprezahost/apis/impreza-host-public-api/revisions/db86511f604f?raw)
