DinD HostingManual
FAQ

Questions people ask before — and after — the first deploy.

63 short answers, each linked to the chapter that explains it. Type to filter, or pick a topic.

General

What is DinD Hosting?

A self-hosted, multi-user platform-as-a-service that runs on one Linux host. You create projects, deploy a static, Node.js, Python or PHP app from git or an archive, attach PostgreSQL or MySQL, and get HTTPS domains, S3 storage, backups and metrics.

The name describes how it is built: the whole platform lives inside a single privileged Docker-in-Docker container, and every user's apps run in a Docker daemon of their own inside it. See Core concepts.

Who is it for?

Teams and individuals who want a Vercel- or Heroku-like workflow on hardware they control: internal tools, client sites, APIs, side projects, workshop or classroom environments, and AI-agent-driven deployments. It deliberately stays small — one host, one app per project.

How do I use it — console, API or agent?

All three, interchangeably. The web console, the REST API and the MCP server are thin wrappers over the same service layer, so authorisation, limits and the audit trail are identical. Create an API token for scripts and agents.

How many apps can a project have?

One. A project is the unit of deployment, like on Vercel. For a second service create a second project — it shares your daemon and your S3 bucket.

Which languages are supported?

Four runtimes: static (nginx), node, python and php (php-fpm + nginx). The platform renders the Dockerfile itself from a template for the chosen runtime; platform.yaml has no key for supplying your own Dockerfile. Other languages would need a new runtime added by the operator. See the four runtimes.

Can I run cron jobs or background workers?

There is no separate scheduler or worker feature — a project runs one container. Run background work inside your app (a scheduler library or a queue consumer in the same process), or trigger it from outside by calling an endpoint of your app from CI or from a cron on another machine.

In which languages is the console available?

English and Ukrainian. The language selector sits in the top navigation and on the login page; the choice is remembered in the browser, and until you choose, the browser's language decides. Text that comes from the server — error details, for example — is shown as received, in English. See Console language.

What do I need to get started?

An account (an admin creates it), the console address and a base domain. Then follow the Quick start — a login, a project and an archive.

Deploying

How do I deploy?

Open the project's Deploy tab, choose archive upload or git repository and press deploy. Over REST it is POST /api/v1/projects/{id}/deployments; over MCP it is deploy_app. The build runs in the background and streams its log. Deploying has the details.

How does the platform know what my app is?

From platform.yaml if you provide one; otherwise from the first matching file at the source root: package.json → node, requirements.txt/pyproject.toml → python, composer.json/public/index.php → php, index.html → static. Zip the folder's contents, not the folder itself — the files must be at the archive root.

Can I deploy from a private git repository?

The clone is non-interactive (GIT_TERMINAL_PROMPT=0), so a repository that wants a username or password cannot be cloned. Either make it reachable without a prompt or build the archive in your CI and upload it — the archive path works for any source.

How big can an upload be?

512 MiB, both for the uploaded file and for the extracted tree. Leave out node_modules, build caches and media. Agents use a single-use upload link for anything over about 3 MiB.

How do I deploy a React or Vue app built with Vite?

The static runtime serves files — it does not run your build. Build locally or in CI, then deploy the output:

yaml
runtime: static
site_dir: dist
spa: true

spa: true makes unknown paths return index.html so client-side routing works.

What happens if a deployment fails?

The old app is replaced only after the new image has been built, so a failed fetch, detection or build leaves the previous version running. The error is shown above the build log. A deployment interrupted by a platform restart is marked failed — just deploy again. See When a deployment fails.

Is there downtime during a deploy?

A short one: the old container is stopped and removed and the new one started, so expect a brief interruption (seconds). Data in volumes, databases and S3 is untouched.

How do I roll back?

There is no rollback button. Deploy the older source again — an older git tag or the older archive. Tag what you ship and pin version to a full x.y.z if you need identical rebuilds.

Can I deploy from CI?

Yes — it is three REST calls with an API token: upload (or give a git URL), start the deployment, poll its status.

bash
curl -fsS -X POST "$BASE/api/v1/projects/$PID/deployments"   -H "Authorization: Bearer $DIND_TOKEN" -H 'Content-Type: application/json'   -d '{"source":{"type":"git","url":"https://git.example.com/me/app.git","ref":"v1.4.0"}}'

Poll GET /api/v1/projects/$PID/deployments until the newest entry is running or failed.

My Node build fails with “tsc: not found”. Why?

The generated Dockerfile sets NODE_ENV=production before npm ci, so npm skips devDependencies. Either move the build tools into dependencies or tell the platform to install them:

yaml
install_cmd: npm ci --include=dev

Runtimes & versions

How do I choose a Node, Python or PHP version?

Add version to platform.yaml: "22", "22.12" or "22.12.1". If you do not, the platform looks for an exact or major hint (.nvmrc, .node-version, engines.node, .python-version, require.php), and finally uses the platform default. The manifest always wins. See Runtime versions.

Why must the version be quoted?

YAML reads unquoted 3.10 as the number 3.1 and silently drops the zero. The platform therefore rejects a float with a message asking you to write version: "3.10". A whole number such as version: 22 is accepted.

Will my app change versions by itself?

Only if it follows a moving version. A short version ("22", "22.12") always builds on the newest matching release and is re-pulled on every deploy, so a rebuild picks up fixes. A full x.y.z is pinned and pulled only when it is not already on the daemon. An app with no version follows the platform default — and moves to a new default on its next rebuild (already-built images keep running until then).

What are the defaults?

Node 24 (the newest LTS when the default was set), Python 3.12, PHP 8.5 and nginx 1.27. Administrators can change them for the whole platform with PLATFORM_DEFAULT_{NODE,PYTHON,PHP,STATIC}_VERSION.

How can I see which version my app was built on?

The top of the build log says # runtime=node version=24 (platform default) and # base image: node:24-alpine (sha256:…), and the App tab shows the base image in use.

Can I use yarn, pnpm, Poetry or another tool?

npm (npm ci, falling back to npm install) and pip are the defaults. Override install_cmd, build_cmd and start_cmd in platform.yaml to run something else — the tool just has to exist in the runtime's base image or be installed by your command.

Configuration & secrets

Where do I put secrets and configuration?

In project variables (the Environment tab, PUT /projects/{id}/env/{key} or set_env). Tick secret for credentials: they are stored encrypted and can never be read back. Use env: in platform.yaml only for non-secret constants that travel with the source. See Environment & secrets.

I changed a variable but the app did not notice.

Variables apply on recreate or deploy. Press recreate app now on the Environment tab (or recreate on the App tab): it replaces the container from the same image with the new environment, in a few seconds, keeping volumes.

Can my build see the variables?

No. Project variables are runtime-only. Put build-time values in platform.yaml (build_cmd/install_cmd) or read the variable when the app starts.

Can I read a secret after saving it?

No — by design. The console shows ••••, REST and MCP return null, and audit rows and logs never contain it. To change a secret, set it again.

Which names are reserved?

PORT and anything starting with PLATFORM_, S3_ or DB_ belong to the platform and are rejected as project variables, so a variable can never shadow a platform value.

My app cannot find its port.

Listen on 0.0.0.0:$PORT. The platform picks a free port in your daemon and passes it as PORT. Binding 127.0.0.1 or a hard-coded number makes the site unreachable.

Domains & TLS

What URL does my project get?

<slug>.<alias>.<base_domain>, for example blog.alex.example.com, reserved when the project is created. Whether it resolves from the Internet depends on how your administrator publishes DNS for the base domain.

How do I use my own domain?

Create an A record for the name pointing at the platform's public ingress address, then add it on the Domains tab. When the pre-check shows passed a route and a certificate follow. After fixing DNS press retry pre-check. See Domains & TLS.

What is the pre-check for?

It proves that the name leads back to this platform — the DNS record resolves (to the configured address, if set) and a marker URL answers with the platform's own id — before a certificate is requested. A wrong record therefore never burns an ACME rate limit.

Why is my certificate stuck on “pending” or “error”?

Common causes: port 80 does not reach the platform (HTTP-01 needs it), the CA's rate limit, or a placeholder contact email (admin@platform.example is refused by every CA). A watchdog retries every five minutes. Ask your administrator to rehearse with Let's Encrypt staging first. See Troubleshooting.

Are wildcard certificates supported?

No. Certificates are issued per domain with the HTTP-01 challenge. Each domain you add gets its own.

Does HTTP redirect to HTTPS?

Yes, once a domain has a certificate. Your app only ever receives plain HTTP from the platform, so do not add your own redirect on the app's port.

My browser says the site is not secure.

The platform is probably in dev TLS mode, where a local certificate authority signs certificates. Import its certificate (data/platform/traefik/certs/dev/ca.crt), or have an administrator switch to prod.

Databases, files & backups

Which databases can I use?

PostgreSQL (new databases run postgres:17-alpine) and MySQL (mysql:8.4). Each database keeps the image it was created from for life; the console shows it next to the engine. See Databases.

How does my app connect?

List the database in platform.yaml (databases: [main]) and the platform injects DB_MAIN_HOST, _PORT, _USER, _PASSWORD and _NAME. Create the database first — variables are only injected for databases that exist when the app is deployed or recreated.

Can I connect to the database from my laptop?

Databases are reachable only on the platform's internal network — there is no public database port. Use the SQL console for queries (one statement, 30-second timeout, 1 000 rows / 1 MiB), take dumps for bulk export, or run tooling from inside your app.

I lost the database password.

It is shown once, when the database is created, and stored encrypted. Your app keeps receiving it through the DB_* variables. For new credentials create a new database and move the data (dump and restore).

Where should uploaded files go?

Not onto the container's filesystem — it is replaced on every deploy. Use your S3 bucket (s3: true injects S3_*), a volume declared in platform.yaml, or the database. See File storage.

Are backups automatic?

No schedule is built in. create project backup (or create_backup, POST /projects/{id}/backups) snapshots every database dump, the app volumes and the last source archive on demand; call it from your own cron or CI for regular copies. A pruner keeps 14 days / 5 per scope by default (admin-editable).

Is my S3 bucket part of a backup?

No. Project backups contain database dumps, volume tars and the source archive. Back up the bucket yourself if it holds the only copy of something important.

What happens to backups when I delete a project?

They are deleted with it, so they cannot bring the project back. Dump your databases and download what you need before you delete.

Security & access

Who can see my projects?

You — and administrators. The ownership check is enforced in the service layer for the console, REST and MCP alike, and anything that is not yours answers not found rather than forbidden.

How are my apps isolated from other users?

Every user has a Docker daemon of their own for apps (projects-<alias>). Sources are streamed to it per deploy and never bind-mounted. Databases are separate containers with their own credentials — in a shared database daemon by default, or in a dedicated db-<alias> daemon when an admin enables one. See Security model.

How are secrets stored?

Platform secrets are generated at first boot into data/platform/secrets (mode 0600). Stored credentials — database passwords, secret variables — are encrypted with a Fernet key from that directory. Passwords for accounts are hashed with argon2id, and API tokens are stored only as hashes.

How long do sessions and tokens last?

A login JWT lasts 8 hours. An API token (dt_…) lasts until you revoke it, and its value is shown only once when created.

Is the Docker API exposed?

No. The main daemon has no TCP listener at all — unix sockets only — and the platform publishes no host ports. The only host-facing surface is one address on a bridge network, and what is forwarded there is the operator's decision.

Is every action logged?

Every mutating action writes an audit row (who, what, which target), and every SQL console statement writes a query log row. Secret values are never part of either. See Audit trail.

API & AI agents

What is MCP and why would I use it?

The Model Context Protocol lets an AI agent call tools. The platform exposes every capability — create projects, deploy, read logs, run SQL, manage domains and backups — as 33 tools, with the same permissions and audit trail as the console. You can say “deploy this folder” and watch it happen.

How do I connect Claude Code?

Create a token on the tokens page, then run the command the dialog shows:

bash
claude mcp add --transport http dind-platform https://<your-platform-host>/mcp/   --header "Authorization: Bearer <dt_… token>"

Keep the trailing slash on /mcp/. See Connect Claude Code.

Can an agent upload a large archive?

Yes, with create_upload_url: a single-use, 15-minute PUT link for up to 512 MiB, bound to one project and file type. The agent curls the file to it from a shell and deploys the returned upload_id. upload_archive is only for archives up to about 3 MiB.

Is it safe to give an agent my token?

It acts with your permissions, so treat the token like a password and create one per tool so you can revoke it individually. Do not pass real secrets through set_env — values travel through the agent's conversation; set credentials in the console instead. For automation that should touch only some projects, ask an admin for a dedicated user.

Which REST endpoints exist?

Everything the console does: projects, uploads, deployments, app lifecycle, variables, databases and SQL, domains, backups, metrics and admin routes under /api/v1. See the route table.

Running the platform

What do I need to run it?

A Linux host with Docker and Compose v2, about 2 CPU and 4 GB RAM as a minimum, and outbound Internet for image pulls. Then make build and make up. See Operations.

What happens after a reboot or crash?

Docker restarts dind-hosting; every inner container has restart: unless-stopped; and on boot the management service's reconciler re-asserts the network, platform containers and every user's daemon — creating what is missing and never destroying data. The operator action after any crash is just make up.

How do I upgrade?
bash
git pull
make build
scripts/upgrade.sh

Data is untouched and migrations run on boot. Roll back by rebuilding the previous commit and running scripts/upgrade.sh again.

How do I back up the platform itself?

Everything durable is in data/. For a consistent copy stop the platform (make down), copy or tar data/ — including data/platform/secrets — and start it again (make up). The in-product backups protect user data, not platform state.

What must my reverse proxy forward?

Ports 80 and 443 (and, if you want the console and MCP on your own name, the console port 8900 or a proxied path) to 10.10.10.10. For big uploads allow a 512 MiB body and disable buffering; for real client addresses enable the PROXY protocol. See Production domains and TLS.

How long are app logs kept?

Only the newest lines: every container's log is rotated at 2 files × 25 MB by default (50 MB at most), so a chatty app cannot fill the disk. Send anything you must keep to your own logging service or to S3. Operators can change the limits with PLATFORM_LOG_MAX_SIZE and PLATFORM_LOG_MAX_FILE; see Container log limits.

Can it run on several hosts?

No — this version is deliberately single-host. Multi-host and Kubernetes deployments, horizontal scaling of the management service and a registry shared between daemons are listed as not in scope.

No question matches.Try fewer words, or search the whole manual with Ctrl K.

Not answered here?

The manual has a chapter for every area, and Troubleshooting maps symptoms to fixes.