Skip to content

The agent, apps & control panels on your VPS

PortalImpreza documentationRead as Markdown ↗

Tutorial 1 showed how to connect an AI to the platform. This one explains what actually lives inside the customer's server: the agent, how apps run, the catalog, custom deploys, and the automatically installed control panels.

Two server personalities

When a customer buys a VPS or dedicated, the server takes one of two mutually exclusive roles, chosen at checkout:

┌──────────────────────────────────┐ OR ┌──────────────────────────────────┐ │ AGENT MODE (Docker) │ │ PANEL MODE (classic hosting) │ │ • Impreza agent runs as daemon │ │ • aaPanel / CyberPanel / │ │ • apps run in Docker containers│ │ CloudPanel / cPanel owns box │ │ • catalog + custom apps + Tor │ │ • NO Impreza agent │ │ • driven by AI / CLI / API / UI│ │ • NO Docker catalog │ │ │ │ • you manage via the panel UI │ └──────────────────────────────────┘ └──────────────────────────────────┘ ▲ most of this tutorial ▲ the "Control panels" section

The two modes are exclusive: a panel server does not run the Impreza agent and has the Docker catalog disabled. You choose at the cart.

What the agent is

The agent is a small Go daemon we install on the VPS (ID format agt_xxxxxxxxxxxx). It is the piece that turns "some VPS" into "an Impreza server". In one sentence:

The agent keeps talking to the Impreza control plane, receives deploy orders, runs them locally via docker compose, and reports the result back.

What the installer (install.sh) leaves on the server:

With the updated VPS interface, Ubuntu and Debian servers that answer through the QEMU guest agent offer Install the Impreza Agent with one click. RHEL-family systems, an unknown OS or a missing guest agent use the manual My Apps installation path instead. A registered agent removes both installation offers. Completed installs show a short My Apps link; job logs are shown only while running or after failure, without HTTP header diagnostics. Reinstalling the OS does not reinstall the Impreza agent automatically: old install jobs and heartbeats from the previous OS no longer mark the new OS as installed. These display changes require the updated portal addon and VPS module; older module releases retain their earlier layout.

  • detects OS + architecture (amd64 / arm64);
  • installs the Docker engine if missing (the agent uses docker compose for every deploy);
  • installs git if missing (used by git-URL deploys);
  • downloads the agent binary to /usr/local/bin/impreza-agent;
  • installs the systemd unit with hardening (ProtectHome=yes, NoNewPrivileges, …);
  • consumes the bootstrap token, receives permanent credentials, writes them to /etc/impreza-agent/credentials.toml (mode 0600);
  • starts the impreza-agent.service unit.

From there the agent polls the control plane: when you (or the AI, or the CLI) create a deployment, the order arrives on the next poll and the agent runs it on the server itself. That is why deploys are private: the code runs on the customer's machine, who keeps root. Impreza never executes your app on our infrastructure.

What the agent handles automatically

ConcernWhat the agent does
HTTPSRuns Caddy, which issues a Let's Encrypt cert for each HTTP app
DomainsCreates/manages routes; allocates a *.imprezaapps.com subdomain when you bring none
Tor / .onionRuns a Tor sidecar, generates the ed25519 keypair, publishes the .onion
Buildsgit clone --depth=1 + docker build, or a build from an uploaded tarball
PortsAllocates a free host port per instance (HTTP apps sit behind Caddy)
LimitsApplies --cpus + --memory per container (default 1 CPU / 512 MB)
So "what is it for": the agent is Impreza's arm inside the customer's server. Without it, nothing can be installed by the platform — AI, CLI and API all answer "no server found on this account".

3 ways to run apps (agent mode)

Catalog (1-click)

The fastest route: apps we packaged and verified. Browse the catalog in My Apps, click Install, pick server + domain, done (~30s). Apps today include WordPress, PrestaShop, n8n, OpenClaw, Vaultwarden, Nextcloud, Matrix, Gitea, SearXNG, Memos, Uptime Kuma, Activepieces, Jellyfin, PhotoPrism, MinIO, Mattermost, Open WebUI, RustDesk, Evolution API plus managed databases (PostgreSQL, MySQL, MariaDB, MongoDB, Redis).

You can install the same app multiple times on one server — each instance gets its own host port. (Exceptions: fixed-protocol apps like RustDesk and the databases are one per server.)

curl -X POST https://api.imprezahost.com/v1/platform/deployments \
  -H "X-API-Key: imp_..." -H "X-API-Secret: ..." \
  -H "Content-Type: application/json" \
  -d '{
    "app_name": "vaultwarden",
    "agent_id": "agt_xxxxxxxxxxxx",
    "domain": "vault.example.com",
    "onion": true,
    "vars": {"signups_allowed": "true"}
  }'
Apps that come up ready: WordPress, Gitea and n8n land already installed, no wizard — the platform runs the install step for you. You open the URL and you are in.

Custom app (your own code)

In the updated My Apps layout, Bring your own server and Deploy a custom app have their own menus below App catalog. Servers is the last menu. Custom deployment still requires Advanced view and a registered server. Expand the native disclosure to review the existing source form; keyboard navigation and the disclosure work without JavaScript. Source analysis and submission options that use JavaScript still require it. Older portal versions keep these forms inside Servers and App catalog until the portal update is released.

For code that is not in the catalog. Four source modes — the platform auto-detects which from the fields you fill:

  • Public image — { "image": "nginx:alpine" }. The agent does docker pull + docker run.
  • Dockerfile via local tarball — the CLI tars the dir, uploads, the agent builds on the VPS.
  • Dockerfile via git URL — { "git_url": "...", "git_ref": "main" }. Private repos: git_auth_method: "deploy_key" (SSH key the platform generates) or "pat" + git_pat (HTTPS token).
  • Manifest — a full docker-compose YAML, treated like a catalog app.
curl -X POST https://api.imprezahost.com/v1/platform/deployments/custom \
  -H "X-API-Key: imp_..." -H "X-API-Secret: ..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "my-app",
    "agent_id": "agt_xxxxxxxxxxxx",
    "git_url": "https://github.com/your-user/your-repo",
    "git_ref": "main",
    "dockerfile_path": "Dockerfile",
    "target_port": 8080,
    "cpus": 1.0,
    "memory_mb": 512
  }'

Or from the CLI, in a directory with a Dockerfile:

cd ~/my-app
impreza deploy --agent agt_xxxxxxxxxxxx --name my-app --follow
# → Packaging → Uploading → Deploying → ✓ Deployed at https://my-app-3f7c9a.imprezaapps.com

Environment variables. Pass them as a vars object in the deploy body (or --env KEY=VALUE on the CLI). For image and Dockerfile deploys the platform generates the compose for you, so every var is injected into your container's environment automatically — no environment: block or env_file to write. (In manifest mode you supply the compose yourself, so wire them up there.) Vars persist with the deployment: an in-place redeploy re-applies them, and you add or rotate a value by passing vars again on the redeploy.

Auto-deploy on every git push

For git_url deploys, connect a push webhook so every commit on the watched branch re-deploys (re-clone at HEAD → rebuild → restart in place). Works with any provider:

  • GitHub (one-click): paste a fine-grained PAT with Webhooks: Read & write — we install the webhook and discard the token.
  • Anything else (manual): connect with no token; we return { payload_url, webhook_token }. Add a push webhook to that URL, sending the token as the GitLab "Secret token" or the X-Impreza-Token header.

Enable it from My Apps (Advanced → 🔗 Connect git for auto-deploy), the AI (impreza_git_webhook_connect), or the REST API.

How apps are reached (network modes)

ModeHow it is reachedExample
http (default)URL with HTTPS via Caddy + Let's Encrypt + subdomain; the app's own port stays on the server's loopback, so a bare IP is refused as the domainAlmost every app
host_portsRaw TCP/UDP on fixed ports, no Caddy/cert/domain; response carries connection_info: {ip, ports, public_key}RustDesk, managed DBs
hybridRaw ports and an HTTP admin UI behind CaddyAdGuard (DNS on :53/udp + HTTPS admin)

Managed databases

PostgreSQL, MySQL, MariaDB, MongoDB and Redis ship as catalog apps in host_ports mode — raw TCP on the VPS public IP, no domain/cert. Deploy returns connection_info: { ip, ports }; connect with the native client (psql, mysql, mongosh, redis-cli). Credentials surface as ADMIN_USER + ADMIN_PASSWORD on the My Apps card (click to reveal) — a strong 20-char password is auto-generated. They ship open to the internet, protected by the password — restrict who can reach each port with an inbound access allowlist.

Domains, redeploy & .onion (what stays stable)

  • Domains are stable. Once an app has a domain it keeps it — an in-place redeploy never touches it, and even uninstall + recreate under the same name reuses the original *.imprezaapps.com subdomain. To change it deliberately, POST /v1/platform/deployments/{id}/domain; this needs agent 0.6.22 or later (domain handover).
  • Redeploy in place: POST /v1/platform/deployments/custom/{id}/redeploy rebuilds the current source and swaps the container with near-zero downtime; domain, port and URL never change. Prefer it over uninstall + recreate.
  • Tor / .onion: any deployment can publish a Tor v3 hidden service alongside its clearnet URL with onion: true. Onion-only deploys work too (onion: true + no domain).

Limits + trust model

  • Cgroups: every custom deploy gets --cpus + --memory at start (default 1 CPU / 512 MB), overridable via cpus + memory_mb, clamped against the VPS RAM.
  • Build context size: tarball uploads cap at 100 MB. Use .dockerignore (the CLI already excludes .git, node_modules, __pycache__, …).
  • API rate limit: 60 req/min/key default (raise per-key in clientarea).
  • Trust: we trust the customer — the VPS is yours, the agent runs your code, cgroup ceilings prevent runaway containers. We do not scan images/Dockerfiles today.

Team permissions

The browser panel uses the signed-in member's permissions on the selected account. Members with View Products & Services can view service and deployment listings. Manage Products & Services also permits the supported app and server operations, including app logs and file inspection. A service permission does not grant account administration or permission to issue API credentials.

Credential management, billing, account-wide diagnostics, domains and generic API access through this panel require the account owner. Unavailable tools are omitted from the list and direct calls are refused. Use the corresponding client-area pages for other permissions granted by your account owner. API keys and OAuth connectors continue to use their separately granted scopes and resource restrictions.

Automatic control-panel installation

This is the alternative to the agent. Instead of the agent + Docker flow, the customer picks a classic hosting panel at checkout that owns the whole server — managing sites, databases, email, DNS and SSL from the panel's own UI.

Remember: it is exclusive mode. A panel server runs no Impreza agent and the Docker catalog is disabled. You cannot have both on one server.

Available panels

PanelCostAdmin portNotes
aaPanelfreerandomized (bt default prints it)Lightweight; optional 1-click stack (nginx + PHP 8.4 + MariaDB). Tolerant of a non-pristine OS.
CyberPanelfree8090OpenLiteSpeed base (LSCache, 1-click WordPress, PHP, MariaDB, DNS, email, SSL). Needs a fresh OS.
CloudPanelfree8443Modern nginx for PHP, Node.js and Python with free SSL. Needs a fresh OS.
cPanel/WHMlicense NOT included2087Installs unlicensed; log in to WHM as root and start the 15-day trial or apply your own IP-bound license.
DirectAdmin appears in the known-panels list but is not automatable today (trials were discontinued and the service will not even start unlicensed), so it stays out of automatic provisioning.

OS compatibility (important)

Each panel runs only on certain operating systems. The cart validates this up front and, if the server OS is not compatible, asks you to reinstall with a supported one first:

  • aaPanel: Ubuntu 20.04/22.04/24.04, Debian 10/11/12, CentOS 7/8, AlmaLinux 8/9, Rocky 8/9.
  • CyberPanel: Ubuntu 20.04/22.04/24.04, Debian 11/12, AlmaLinux 8/9, Rocky 8/9, CentOS 7/8.
  • CloudPanel: Debian 11/12/13, Ubuntu 22.04/24.04.
  • cPanel/WHM: AlmaLinux 8/9/10, CloudLinux 8/9/10, Ubuntu 24.04 only.
Two layers of checking: the cart does a best-effort validation by template name; the real one is a gate inside the server that reads /etc/os-release at boot and aborts the install on an incompatible OS instead of half-installing.

The flow, from checkout to "ready"

checkout provisioning credentials ──────── ──────────── ─────────── pick the panel VPS: auto-installs when ready, the My Apps tile in the → a few minutes after → card shows the login URL cart payment + user + password (reveal) dedicated: our team provisions after the hardware is racked
  1. At the cart, pick the panel tile. (For aaPanel there is also a bare profile = panel only, fast; or full = panel + nginx/PHP/MariaDB stack.)
  2. On a VPS, the install runs automatically a few minutes after payment. On a dedicated, our team provisions it once the hardware is racked.
  3. The OS gate runs on the server; if incompatible, the install aborts and the clientarea shows a "reinstall with a supported OS" card.
  4. When done, the panel appears in My Apps with the login URL, the username and the password (click to reveal — stored encrypted on our side).
  5. cPanel/WHM exception: no own password — log in to WHM (:2087) with the server's root account, accept the EULA, then start the trial or apply your license.

Reinstall / leave panel mode

  • Reinstall is self-service from the clientarea (the OS list is filtered to compatible ones). Useful if the first attempt failed on an incompatible OS.
  • Uninstall exists as an exit route — to leave panel mode and go back to a bare server.
There is no REST/CLI/MCP endpoint to provision a panel. Panels are only a checkout + clientarea feature. The AI does not install panels — it works in agent mode. A customer who wants both AI/Docker and a panel needs separate servers.

Which mode should you choose?

You want to…Choose
Ship your own Docker / code / git appsAgent mode
Deploy by chatting with an AIAgent mode + MCP
Ready-made catalog apps (WordPress, n8n, Vaultwarden…)Agent mode (1-click catalog)
A classic hosting panel (sites/email/DNS via UI)Panel mode (aaPanel / CyberPanel / CloudPanel / cPanel)
Host many client sites with cPanelPanel mode (cPanel/WHM — bring your license)

Project preparation uses the same analyzer in the portal, REST and hosted/local MCP. Supply requirements.txt with an explicit start command for Python, or composer.json with an optional PHP public folder. An empty requirements.txt is valid; omitting the file leaves Python unassessed. Review the returned options before applying them. Analysis neither downloads the repository nor runs a build or deploy.

Build PHP without a Dockerfile

PHP Composer builds: choose PHP 8.4 + Composer + Apache in the portal, or build_strategy=php_composer with mode=dockerfile in impreza_deploy_custom. Select a Git repository or uploaded source archive, project_dir (default .) and php_document_root (default public). The public folder must contain index.php. Local MCP requires 0.30.0+. The recipe uses php:8.4-apache-bookworm, Composer 2 and non-root user app (UID 10001). Apache starts in the foreground and serves the configured target_port, default 8080; allowed ports are 1024-65535. A custom start_command is not accepted for this recipe.

Commit a regular composer.json up to 32 KiB and matching composer.lock up to 2 MiB in the selected project folder. Composer validates the lockfile, installs production dependencies with scripts and plugins disabled, optimizes the autoloader and checks actual PHP/extension requirements. Production packages require public HTTPS zip/tar distributions without embedded credentials, query strings or fragments. Custom repositories, Composer plugins/installers, custom vendor paths and private dependency credentials require a Dockerfile. The image adds intl, zip, pdo_mysql, pdo_pgsql and opcache to its bundled PHP extensions; other extensions require a Dockerfile. Missing lockfiles, unavailable dependencies or incompatible PHP/extensions fail the build before replacing the running application. The lockfile fixes package selections, not the base image or every external artifact.

php_document_root is a relative subfolder such as public, web or site/public, up to 120 ASCII characters and five segments. It cannot be the project root, contain hidden/parent/vendor/node_modules segments, or use symlinks; symlinks inside the public tree are also rejected. Apache serves existing files there and sends missing paths to index.php with the original request URI. Directory listings and .htaccess overrides are disabled; dotfiles are denied. This recipe does not build frontend assets, initialize frameworks, run Composer scripts or database migrations, create databases, or start workers. Prepare application configuration and public assets in your project, and use Dockerfile for applications needing additional setup. Persist application data through explicit volumes as usual; redeploy can replace container-local files.

The source copy excludes .git, .env, .env.*, vendor, node_modules, auth.json and .composer; dependencies are installed afresh. Review the public folder yourself: every other file there can be served, and these exclusions are not a general secret scanner. Runtime environment variables are separate from installation; public_build_vars remains npm-only. Compose 2.17+ with BuildKit is required. The existing agent build executor is reused; no new agent binary is needed for this recipe.

PHP health checks send HTTP GET to 127.0.0.1 at the configured target_port and require 2xx without redirects. healthcheck_path defaults to /. To require health before success, choose an explicit path and require_healthy_start=true, with startup_timeout_seconds from 30 to 600 (default 60), on agent 0.6.3+. Without this option, an initial deployment may finish with a startup warning. A failed replacement can recover an eligible previous healthy release; this does not undo database writes. Public folder, project folder, recipe and health settings are saved at creation, returned by deployment reads and inherited by new previews; redeploy uses the saved snapshot. Create a new deployment to change those settings.

For preflight, send composer_json and optionally php_document_root to impreza_prepare_project, or select composer.json under Prepare your project in the portal. php_composer_recipe reports not_assessed without composer_json, blocked for unsupported configuration, and needs_verification otherwise. A conditional deployment_options entry includes php_document_root and default port 8080. Framework hints for PHP, Laravel or Symfony are metadata hints, not a compatibility guarantee. Only supplied configuration is examined: the analyzer does not inspect composer.lock, fetch Git, verify the public directory, install dependencies, execute scripts or create resources. Review the suggested settings before applying them.

Example MCP arguments. Replace the repository and server, and implement a /health route returning HTTP 2xx in your application:

{
  "name": "php-site",
  "agent_id": "agt_your_server",
  "mode": "dockerfile",
  "build_strategy": "php_composer",
  "git_url": "https://github.com/your-team/your-php-project.git",
  "git_ref": "main",
  "php_document_root": "public",
  "target_port": 8080,
  "healthcheck_path": "/health",
  "require_healthy_start": true
}

Build Python without a Dockerfile

Python pip builds: choose Python 3.13 + pip in the portal, or build_strategy=python_pip with mode=dockerfile in impreza_deploy_custom. Use a Git repository or uploaded source archive, project_dir (default .), requirements.txt and an explicit start_command. Local MCP requires 0.29.0+. The recipe uses python:3.13-slim-bookworm, a virtual environment and non-root user app (UID 10001). Default target_port is 8000. The production command must listen on 0.0.0.0 at that port; PORT and HOST are runtime variables. For Flask, include Flask and gunicorn in requirements.txt and use exec gunicorn --bind 0.0.0.0:$PORT app:app. For FastAPI, include fastapi and uvicorn and use exec uvicorn app:app --host 0.0.0.0 --port 8000. The module and application names must match your project. Prefix a single server command with exec to forward shutdown signals. Commands are one line, up to 1000 UTF-8 bytes, saved as public deployment configuration: never include credentials.

requirements.txt must be a regular UTF-8 file up to 32 KiB; an empty file supports standard-library apps. The supported subset is flat PyPI package names, version specifiers, extras, markers and comments. pip resolves and validates the dependencies during the build. Options, includes, URLs, local projects, continuations, Poetry/uv lockfiles, system packages and private build credentials require a custom Dockerfile. Pin direct and transitive versions for repeatable installs; a requirements file or source hash alone does not freeze dependencies or the base image. The selected folder is copied with .git, .env, .env.*, .venv, venv, __pycache__, .pip and pip.conf excluded. This is not a general secret scanner. Runtime variables are separate from build/install; public_build_vars is npm-only. Compose 2.17+ with BuildKit is required; no new agent binary is needed for this recipe.

Python health checks use HTTP GET on 127.0.0.1 at target_port, requiring 2xx without redirects. healthcheck_path defaults to /; unlike legacy Node behavior, a Python 404 is not healthy. To require health before success, set an explicit path and require_healthy_start=true, with startup_timeout_seconds from 30 to 600 (default 60) and agent 0.6.3+. Without this option, a first install may finish with a startup warning. Failed replacement recovers an eligible previous healthy release; this does not undo database changes. Command, folder, recipe and health settings are saved at creation, returned by deployment reads and inherited by new previews; redeploy rebuilds using the saved recipe. Create a new deployment to change these settings. No automatic framework setup, database migrations or continuous rollback is provided.

For Python preflight, send requirements_txt and start_command to impreza_prepare_project, or select requirements.txt and enter the command under Prepare your project in the portal. The python_pip_recipe result reports not_assessed without requirements, blocked for unsupported format or missing command, and needs_verification otherwise. A conditional deployment_options entry carries the explicit command and default port 8000. Only supplied metadata is examined: it does not fetch the repository, verify the selected folder, install dependencies, execute the command or create resources. Apply a suggested option only after reviewing it.

{"name":"python-api","agent_id":"agt_...","mode":"dockerfile","git_url":"https://github.com/example/api.git","build_strategy":"python_pip","start_command":"exec gunicorn --bind 0.0.0.0:$PORT app:app","target_port":8000,"healthcheck_path":"/health","require_healthy_start":true}

uv projects

Local MCP 0.34.0+ supports python_package_manager=uv@0.12.15 with build_strategy=python_pip in the portal, REST and hosted/local MCP. Keep start_command explicit. Upload or commit pyproject.toml (32 KiB maximum) and uv.lock (2 MiB maximum) in project_dir. The recipe uses Python 3.13, a fixed uv binary, a non-root user and uv sync --locked --no-dev --no-editable. It refuses stale locks and verifies that dependency installation did not change the project or lockfile.

Public PyPI packages and the selected root project are supported. Custom indexes, URL/Git dependencies, uv workspaces, dependency-group includes and custom uv resolution settings require a Dockerfile. Source uv.toml, .python-version, local virtual environments and dotenv files are excluded. Python downloads are disabled. Existing pip deployments keep their saved recipe. Upload inspection and saved plans identify pyproject projects, but full TOML/dependency validation happens during build; inspection alone does not establish readiness. Check deployment status and your configured health endpoint after applying. Missing/invalid files fail the build; a failed replacement retains or recovers the preceding application under the existing rollback rules. No automatic database migration is performed.

Build Node.js without a Dockerfile

Opt in to Node.js npm builds without a repository Dockerfile: portal → Advanced → Deploy a custom app → Git repository → Build method → Node.js 24 + npm. REST/custom deploy and MCP impreza_deploy_custom use build_strategy: "node_npm" with the existing Git or uploaded-context Dockerfile mode. Local MCP requires 0.14.0+. The project must have package.json and package-lock.json in the selected project folder and a production start script. The recipe uses Node 24, npm ci --engine-strict, an optional npm run build, production dependency pruning and npm start as user node. It requires Docker Compose 2.17+ with BuildKit. For npm workspaces, pinned pnpm/Yarn and build credentials, see the project plans guide; local MCP requires 0.34.0+. The recipe excludes .git, node_modules, .env, .env.* and .npmrc when copying source; this is not a general secret scanner. Keep credentials out of source code. Set the container HTTP port (default 3000 when omitted), listen on 0.0.0.0 and keep runtime PORT overrides consistent. The startup probe checks HTTP / for a response below 500, not application correctness. Recipe snapshots are reused for Git redeploys and new previews; the repository is rebuilt each time. Uploaded contexts retain their existing one-use/redeploy limitations. A build failure retains the previous healthy runtime through the existing deployment-safety flow.

Requirements: Docker Compose build specification and Node.js release support.

Deploy from a project subfolder

Deploy an independent npm app from a subfolder: set project_dir (default .) with node_npm or node_npm_static, or choose Project folder in the portal. For example, apps/site must contain its own package.json and matching package-lock.json; static_output_dir is relative to that folder. Only the selected folder is copied into /app for npm installation and builds. The repository/upload remains the Docker build context, so its root .dockerignore still applies. Paths allow up to 120 characters and five non-hidden segments; parent, node_modules and symlink components are refused. Missing folders or failed builds retain the previous healthy runtime. The project_dir value is returned by deployment reads, saved at creation and inherited by new previews; redeploy reuses the saved recipe. Existing snapshots default to .; changing the folder requires a new deployment. This supports independent apps in one repository, not shared workspaces or dependencies outside the folder; use a custom Dockerfile for those. The analyzer does not discover subfolders: supply the selected app metadata and review Project folder yourself. Local MCP requires 0.17.0+; no agent upgrade is required beyond the existing build executor.

Require a healthy first deployment

Required healthy start: opt in with require_healthy_start=true, an explicit healthcheck_path and build_strategy=node_npm, python_pip or php_composer. In the portal, enable Require a healthy start. Agent 0.6.3 or newer must be reported in Servers; update it explicitly and wait for the next heartbeat. startup_timeout_seconds accepts an integer from 30 to 600, default 60. The budget starts after containers are created and includes the stable health observation; Git fetch, dependency/image builds and recovery have separate time limits. Every running service must report Docker healthy and at least one serving container must exist. HTTP redirects, authentication errors and timeouts do not pass the configured 2xx probe. When the first install fails, its containers are removed and persistent volumes are preserved; the failure includes diagnostic logs. A failed replacement recovers a verified healthy previous release when available. Recovery and manual rollback use the saved release startup policy, so a slow healthy release retains its original deadline. Failure recovery does not undo database writes or mutable data. The control plane refuses incompatible agents at creation, never dispatches the policy without startup-health-v1 support in the current poll, and accepts success only with a matching health confirmation. Previews inherit the policy; redeploy keeps the snapshot. Deployment reads expose startup_health. Choose a new deployment to change the saved policy. Omission or false preserves legacy behavior; a startup timeout requires the option enabled. The public option is for generated Node, Python and PHP builds. Python MCP inputs require 0.29.0+; PHP requires 0.30.0+. This does not provide zero-downtime switching or continuous automatic rollback after startup. Local MCP input support requires 0.20.0+. Existing agents update only when the customer runs the updater.

The timeout starts after Compose starts the containers; source retrieval and image builds have separate limits. Read saved settings in startup_health on the deployment response.

{"name":"my-api","agent_id":"agt_...","git_url":"https://github.com/example/api.git","build_strategy":"node_npm","healthcheck_path":"/health","require_healthy_start":true,"startup_timeout_seconds":120}

Choose a Node health endpoint

Node health checks: with build_strategy=node_npm, optionally set healthcheck_path (for example /health or /api/ready), or fill Health check path in the portal. The generated Docker health check sends HTTP GET to 127.0.0.1 on target_port inside the container and requires status 200-299; it does not follow redirects or send credentials. The route must work without login and should report the dependencies your app needs to serve requests. Use / or a path of at most 200 ASCII characters; segments start with a letter, digit, underscore or hyphen and may then include dots or tildes. A trailing slash is allowed. URLs, query strings, fragments, percent escapes, spaces, parent segments and repeated slashes are refused. Omit the field (leave it blank in the portal) to keep the legacy / probe accepting statuses below 500; explicitly setting / requires 2xx. Existing deployment snapshots remain unchanged. The saved healthcheck_path is returned by deployment reads, inherited by new previews and retained on redeploy; changing it requires a new deployment. Static sites keep their index.html probe; custom Dockerfiles define their own HEALTHCHECK. The probe runs every 5 seconds with a 2-second request timeout. With required startup disabled, agents 0.6.2 and later observe startup for up to 60 seconds: a failed replacement recovers a verified healthy previous release when one exists. Without that recovery target, a first install can still complete with a startup warning, so inspect health and logs before treating it as ready. This default policy does not provide continuous rollback, zero-downtime routing or external uptime checks. To enforce a startup deadline, enable required healthy startup with agent 0.6.3+. Local MCP inputs require 0.19.0+; healthcheck_path alone needs no agent update beyond 0.6.2.

{"name":"my-api","agent_id":"agt_...","git_url":"https://github.com/example/api.git","build_strategy":"node_npm","target_port":3000,"healthcheck_path":"/health"}

Public build variables

Public build settings: generated node_npm and node_npm_static recipes accept public_build_vars, an object of string values available only to npm run build (including its prebuild/postbuild scripts). For example, {"VITE_API_URL":"https://api.example.com"} lets Vite compile a public API URL into the site. Names must start with VITE_, NEXT_PUBLIC_ or PUBLIC_, contain only uppercase letters, digits and underscores, and be at most 128 characters. Limits: 20 entries, 4096 UTF-8 bytes per value and 16384 bytes for names plus values. Empty strings are preserved; NUL is refused. Values are literal data, without shell or variable expansion. The portal exposes Public build variables as KEY=value lines; do not add surrounding quotes, which would become part of the value. These values are public and may appear in bundles, image metadata and logs: never send passwords, tokens or secrets. They are not supplied to npm ci or automatically added to runtime variables. Runtime vars still configure the running container and do not rewrite compiled browser files. Settings are saved at creation, exposed on deployment reads, inherited by new previews and reused on redeploy. Preview runtime inheritance/overrides do not change this build snapshot; create a separate deployment for different public build values. Existing snapshots default to no public build values. Other build settings, private package configuration and secrets require a custom Dockerfile. Local MCP requires 0.18.0+; no agent update is required beyond the existing build executor.

Deploy a static npm site

Static npm sites: choose build_strategy=node_npm_static with a Git/context source (mode=dockerfile), or Static site + npm in the portal. Requires an independent npm package in the selected project folder, matching package-lock.json and a build script producing index.html in the selected output folder (dist by default). Node 24 installs dependencies and runs the build; unprivileged Nginx serves only the selected output folder with configurable SPA fallback. No start script is required. Default target_port is 8080. Compose 2.17+ and BuildKit required. No SSR or server functions. npm workspaces and explicit build credentials follow the project plans guide. Runtime variables (including VITE_* and PORT) do not rewrite static bundles or change the configured Nginx port. Use static_output_dir (default dist) to choose a relative output folder containing index.html, and static_spa (boolean, default true) to choose SPA fallback or 404 for unknown routes. The portal exposes both fields. Paths are limited to 120 characters and five segments, without hidden, parent or node_modules segments; symlinks in the output or its parent path are refused. These settings are chosen at creation, stored in the recipe snapshot, exposed as static_options and carried to new previews. Redeploy reuses the saved recipe; changing these settings on existing deployments requires creating a new deployment. Existing snapshots keep their original recipe. Local MCP option inputs require 0.16.0+. Use public_build_vars for supported public settings; other build-time configuration requires a custom Dockerfile. Source .env/.env.*/.npmrc/.git/node_modules are excluded; review all generated files because the output folder is public. Missing index.html and symlink output fail the build. Existing preview/redeploy snapshots preserve the strategy. Local MCP requires 0.15.0+; no agent upgrade is needed beyond the existing build executor. The analyzer returns static_npm_recipe and conditional deployment_options; metadata is not proof of a static, working build.

See the Vite deployment guide and unprivileged Nginx image documentation.

For a stack of public container images, use the Compose import guide to review services, variables and persistent volumes through the same portal and MCP flow.

To inspect uploaded files and resume from saved build options, use project plans. Review an option before choosing the target server and runtime settings.

For local projects without Git, use source uploads to retain an archive and select its version for creation or redeploy.

Prepare your project

Prepare project configuration before deploying: in My Apps → Advanced → Deploy a custom app → Prepare your project, select package.json and/or a Dockerfile (32 KiB each). MCP: impreza_prepare_project with package_json, dockerfile and optional dockerfile_path. REST: POST /v1/platform/deployments/custom/prepare (read scope). Analysis identifies Node.js, Next.js or Vite hints, available build/start script commands and explicit TCP ports in the final Dockerfile stage. Review findings and optionally copy a unique declared port into the deploy form. It does not fetch Git, execute scripts, generate Dockerfiles, inspect secrets or create resources. Choose an existing Dockerfile or the opt-in Node npm recipe for supported projects. The node_npm_recipe result reports not_assessed without package metadata, blocked for a workspace declaration, incompatible packageManager or missing start script, and needs_verification otherwise. Its blockers list explains each detected impediment; unverified lists checks still required. Portal findings show the same guidance. deployment_options provides reviewed parameter subsets for the existing custom-deploy call (mode, build_strategy, Dockerfile path when applicable, and target_port only when known or defaulted). Each option requires_review; port_basis distinguishes dockerfile_expose, recipe_default and unconfirmed. The Node recipe uses its own default port 3000, not the supplied Dockerfile port. In the portal, Use buttons copy the chosen build settings and switch to Git only after a click; a stated port replaces the form port, while an unconfirmed port leaves it unchanged. Repository, credentials, server and runtime variables still need review; copying settings never deploys. MCP clients can use the same parameters with separately supplied source and server details after review. No detected blocker is not build readiness: lockfile, Node 24 engines, production start behavior, runtime dependencies, HTTP listener and build requirements remain unverified. Runtime packages must survive removal of devDependencies. This server-side analysis is also available through existing local MCP clients with impreza_prepare_project; no client update is required for these findings. analysis_id is a fingerprint of submitted metadata, not a saved or executable deployment plan. Source/commit, HTTP readiness, framework compatibility, lockfiles, environment variables and capacity must still be verified. Never send .env or credentials; inspect configuration before sending. Hosted MCP uses the server tool; local MCP requires the release containing impreza_prepare_project.

Deploy a Git repository

In My Apps, switch to Advanced view, open Deploy a custom app and expand its source form. On an older portal, find this form inside App catalog. Choose Git repository, enter its HTTPS URL, branch or tag (default: main), and Dockerfile path (default: Dockerfile). The default method requires a Dockerfile; supported npm applications can select the generated Node recipe above. Builds use the repository root as their context.

Select a server, set the port your HTTP application listens on inside the container, and review CPU, memory, domain, runtime environment variables and persistent storage before deploying. Builds run on that server. Variables in this form are runtime values, not build secrets. Do not embed credentials in the URL.

For a private github.com or gitlab.com repository, choose Private — GitHub/GitLab access token and enter a read-only repository token (GitHub Contents: Read or GitLab read_repository). The token is stored encrypted and is never shown again. Renew it later from the app card Git connection if it expires or is revoked. For SSH repositories, choose Private — SSH deploy key and enter git@host:team/app.git or ssh://git@host:2222/team/app.git. Create the app, open its Git connection, copy the public key and add it to the repository’s Deploy keys with read-only access. The first build may fail before the key is registered; wait for it to finish, then select Key registered — redeploy. The public key stays available in the card. To rotate it, generate a new key, register that key before redeploying and remove the old key from the repository. Rotation does not interrupt running containers. Private keys are never displayed. Local uploads and manifests remain available through CLI, REST or MCP. For the same public Git source through MCP, use impreza_deploy_custom with git_url, git_ref and dockerfile_path; omit image. For token-based private Git, also set git_auth_method=pat and git_pat. For SSH, use git_auth_method=deploy_key with an SSH URL and omit git_pat. Check the app card and deployment result after queuing. A missing branch or Dockerfile fails the attempt; the form does not test repository availability before submission.

See deployment safety and rollback for recovery behavior and agent updates for existing servers.

New Git previews preserve the parent app’s Dockerfile path. When a webhook supplies a commit, both the first build and later pushes request that exact revision (agent 0.6.1+); an unavailable commit fails instead of deploying another revision. New private previews use the current parent Git credential at clone time, without copying it into app variables. Rotate the token or SSH key on the parent, then redeploy the preview. The parent must still exist on the same account and agent, with the same repository and authentication method. Existing previews with their own credentials keep them; retire and recreate older previews to adopt the parent connection. Preview reads expose git_credential_source as the parent deployment ID for linked private previews, otherwise null.

Preview variables

In My Apps → Advanced, open Preview settings on the parent Git app card. Connect its push webhook in Auto-deploy, enable branch previews and set the lifetime and maximum count. Other branches can receive an ephemeral .onion preview on your server. Exceeding the cap retires the oldest preview.

New previews inherit no application variables by default. Enter only the names of variables safe for branch code, such as APP_MODE. Leave the list empty to inherit none. Identity, port and resource variables are regenerated for the preview. Never select production database URLs, passwords or tokens. Configure separate test credentials through the preview app’s Environment variables.

The list applies only when a preview is created. Existing previews keep their configuration on later pushes, including values copied before this change. Review and replace those values or retire and recreate the preview. Changes to the parent values do not synchronize into an existing preview. Clearing the list changes future inheritance only.

Through REST or MCP impreza_api_call, send POST /v1/platform/deployments/custom/{id}/previews/settings with {"previews_inherit_vars":["APP_MODE"]}. Changing this list requires deploy scope. Omit the field to preserve it, or send an empty array to clear it. Read the saved names with GET /v1/platform/deployments/custom/{id}/previews → settings.inherit_vars; values are not returned there.

This controls copied runtime variables. It does not isolate shared external services, secrets embedded in source code or images, or access already configured inside an application. Check the deployment result and current app behavior before sharing the preview.