Ship your first app

From code or image
to a working URL.

Choose the path that fits your app. Deploy in the console, use the command line, or ask an assistant connected to DeepHost.

Console quickstart

Deploy an image

Use an image already pushed to your organization's DeepHost registry. A GitHub connection is not required.

  1. Open the console and select your organization.
  2. Choose New application → Registry image, or open an existing app and choose Deploy image.
  3. Enter the app name and full image reference, such as REGISTRY/YOUR_ORG/my-app:v1. The form shows your organization's registry prefix. A @sha256:… reference also works.
  4. Set Container port and Health check path to match your app. The defaults are 8080 and /. Add an optional domain and non-secret environment values if needed.
  5. Choose Deploy image. Keep the returned Release ID, wait for Image is ready, then choose Open application and check its response.

Deploy a registry image

Submission starts a deployment. Ready means the expected image release is selected and its workload passed readiness checks. Open the URL to confirm your application behaves as expected.

Tags are resolved to an immutable digest at deployment time. Pushing a replacement tag will not change a running release; deploy the tag again to create a new release. Image deployments go directly to a Release, with no source Build to wait for.

New apps receive a platform hostname. On an existing app, leaving the domain or environment settings empty preserves the current values. Entering a domain replaces the requested hostname list; review the existing app before changing it.

Image requirements

SettingWhat your app needs
HTTP listenerListen on 0.0.0.0 at the configured port. DeepHost sets PORT for you.
Health pathA path such as /health that returns a successful HTTP response. Do not include a query or fragment.
Architecture on VKElinux/amd64. Build explicitly for this platform from an ARM laptop.
Files and userRun as non-root UID/GID 10001. The root filesystem is read-only; use /tmp for up to 64 MiB of temporary files.
Resources on VKEOne active instance. Requests: 100 millicores and 128 MiB. Limits: one CPU and 512 MiB.
Registry and environmentYour organization's DeepHost registry; production deployments. Image-based pull-request previews are not available.

Keep passwords and tokens out of the form. For existing app-scoped secret references, use the CLI's --env-secret option or MCP's env_secrets field.

Private image storage

Build and push

From a DeepHost checkout, run task cli and use bin/deephost. Docker and a running Docker engine are needed for building and pushing.

bin/deephost registry info --org YOUR_ORG
bin/deephost registry login --org YOUR_ORG
docker buildx build --platform linux/amd64 \
  --tag REGISTRY/YOUR_ORG/my-app:v1 --push .

Replace REGISTRY and YOUR_ORG with the values returned by registry info. Use the organization's registry slug, which may differ from its display name. Login opens browser approval and passes the password directly to Docker.

To push a compatible image you already built:

docker tag my-app:latest REGISTRY/YOUR_ORG/my-app:v1
docker push REGISTRY/YOUR_ORG/my-app:v1
docker pull --platform linux/amd64 REGISTRY/YOUR_ORG/my-app:v1

User Docker logins expire after 24 hours. Manage them in Registry access. Logging out locally removes the saved login; revoking it in the console stops its access. Deployed apps use their own read-only pull access, so user login expiry or revocation does not interrupt them.

Command line

Deploy with one command

bin/deephost deploy \
  --image REGISTRY/YOUR_ORG/my-app:v1 \
  --org YOUR_ORG --name my-app \
  --port 8080 --health-path /health

The hosted flow opens console sign-in and organization consent. Its deployment credential stays in memory. Docker login and deployment sign-in are separate; a registry password is never a deployment argument.

The command waits for the selected image release and checks its public health URL twice. Add --no-browser to open the printed sign-in link yourself, or --no-wait to submit without waiting. A pending URL is not a readiness result.

Use --domain HOST for a custom hostname, --env NAME=VALUE for non-secret settings, and --env-secret NAME=SECRET:KEY[:ENV] for an existing app-scoped secret reference. Use --console https://YOUR_CONSOLE on another installation.

Connected assistants

Deploy through MCP

Connect the DeepHost plugin or configure your client with this console's /mcp address, then finish OAuth and select the organization. Start with deephost_list_apps and deephost_get_registry to confirm the account and image prefix.

Call deephost_deploy_image with the pushed image:

{
  "name": "my-app",
  "image": "REGISTRY/YOUR_ORG/my-app:v1",
  "container_port": 8080,
  "health_path": "/health",
  "environment": "production"
}

Keep the returned release_id, image digest and urls. Check deephost_get_app and deephost_list_releases, then make two real requests to the public URL. No registry credential belongs in the tool call.

Use logical app names: app hello in organization acme is passed as hello, not acme-hello. If a newly released tool is missing, refresh your client's tool list or reconnect the existing plugin.

Fast code loop

Run a quick job

Send a TypeScript or Go handler and receive its result in one call. New job apps have no public hostname. The prepared release stays available for the next input.

export default async function fetch(request: Request): Promise<Response> {
  const { values } = await request.json() as { values: number[] };
  return Response.json({ total: values.reduce((sum, n) => sum + n, 0) });
}

Save this as handler.ts, build the CLI with task cli, then run:

bin/deephost run --file handler.ts --org YOUR_ORG --name sum-job \
  --input '{"values":[10,20,30]}' --repeat 3 --json

The CLI opens console sign-in when needed, prepares once, and executes the same release three times. Each line contains a fresh result with preparation, worker loading and execution timings. Use handler.go with an importable package exporting Handler for Go. Set --timeout in seconds for longer work.

Through an assistant

Ask: “Run a private TypeScript job called sum-job that adds 10, 20 and 30. Then reuse its exact release to add 4 and 5.”

The assistant calls deephost_run_code with name, framework, files and request body. For later inputs, deephost_run_app takes name, the returned release_id and the new body. Reuse skips upload and preparation; each call executes the code again.

Jobs run synchronously: there is no background queue or saved result history. A timeout or lost connection can happen after execution, so inspect the effect before rerunning. DeepHost does not automatically retry execution.

Request and response bodies are limited to 64 KiB, headers to 64 entries and 8 KiB, and the invocation deadline to 1–300 seconds (30 by default, also capped by the handler settings). Source uploads support up to 128 files and 1 MiB; the CLI run command accepts one source file. Warm workers may be evicted, and this shared runtime is for trusted code.

App, Build and Release records remain for reuse until deleted. Existing apps retain their current domains; use a separate job app when private execution is wanted. Identical source reuses a viable matching Build; changing back to a previous version can reuse its release. Small TypeScript uploads skip the build pod; Go builds use a seeded compiler cache.

Small services

TypeScript and Go handlers

Start locally without an application Dockerfile:

bin/deephost init --runtime typescript --dir hello
bin/deephost dev --dir hello

Use --runtime go for Go. Local TypeScript needs Node 22.18+; Go needs its toolchain. TypeScript exports a fetch function using Web Request/Response. Go exports Handler(http.ResponseWriter, *http.Request) from an importable package.

For hosted deployment without a local build setup, call deephost_deploy_files:

{
  "name": "hello",
  "framework": "typescript",
  "files": {
    "handler.ts": "export default function fetch(request: Request): Response { return Response.json({message: 'Hello from DeepHost'}); }"
  }
}

For Go, use the same tool with an importable handler package:

{
  "name": "hello-go",
  "framework": "go",
  "files": {
    "handler.go": "package handler\n\nimport \"net/http\"\n\nfunc Handler(w http.ResponseWriter, r *http.Request) { w.Header().Set(\"Content-Type\", \"text/plain\"); _, _ = w.Write([]byte(\"Hello from DeepHost\\n\")) }\n"
  }
}

Inline deployment accepts up to 128 UTF-8 files and 1 MiB total. Use Git for larger projects. Keep credentials, binaries and dependency directories out of source payloads.

Source deployment returns a Build ID. Wait for that Build to succeed, then verify its ready, selected Release and public response. The basic TypeScript adapter runs erasable TypeScript and does not install dependencies automatically.

Repository workflow

GitHub pushes and previews

Connect GitHub in the console, create an application, then deploy a repository revision from its detail page. Choose the framework and any monorepo path. A full commit SHA fixes the source version; private repositories use your connected GitHub account.

On the app's Automatic deployments panel, choose its repository and production branch, then enable branch pushes and pull-request previews. The connected user must administer the repository to configure its webhook.

Matching pushes update production. Same-repository pull requests targeting the configured branch get a separate app and a stable preview URL. Closing or retargeting the pull request removes its preview. Fork pull requests are skipped, and only preview-scoped environment values are copied.

MCP offers the same flow through deephost_deploy_app, deephost_configure_github_deployments and deephost_get_github_deployments. Keep GitHub access tokens out of browser forms and tool arguments.

After deployment

Verify and roll back

  1. Keep the submitted Release ID for an image, or the Build ID for source. For source, wait for the Build to succeed and find its matching Release.
  2. Confirm the expected Release is ready, selected by the app and has positive traffic weight. For images, its digest must match the deployment result.
  3. Request the public URL twice. Check expected content, X-Dh-Release, and X-Dh-Runtime (container for images).
curl --fail-with-body --max-time 45 -i https://YOUR_APP_URL/health
curl --fail-with-body --max-time 45 -i https://YOUR_APP_URL/health

To roll back, choose a previous release in the app's release list and promote it. For images, an inactive release first starts its workload and passes health checks before traffic switches. Wait for that release to become selected and verify the URL again.

When a deploy needs attention

Troubleshooting

The registry or image option is unavailable

The installation's registry must be enabled. Confirm your selected organization and ask the platform operator to check registry configuration. Image deployment does not require GitHub.

The image could not be found or is rejected

Use the complete reference shown by registry info, with your organization's prefix and an existing tag or digest. Push the image before deploying. External private registries and another organization's images are not accepted.

The app stays pending

Check that the image supports Linux amd64, can run as UID10001, and does not need to write to the root filesystem. It must listen on 0.0.0.0 at the configured port and return success from the health path. Inspect the existing Release before submitting another deployment.

I lost the deployment response

Open the app and inspect its latest release and digest before retrying. The server may have accepted the first request. Repeated clicks can create additional releases.

Docker says the login expired

Run registry login again and approve it in the browser. Existing deployed apps use independent pull access. Never paste a Docker password into the console deployment form or an MCP call.

The release is ready but the URL is wrong

Check the app's domains and selected Release. A custom hostname needs DNS pointing at this installation. Confirm the expected release response after DNS is ready; a ready workload alone does not prove a custom hostname works.