The flow
In the dashboard, New app walks through three steps:- Where is it from. A connected provider’s repository list, or a pasted URL (with a one-shot deploy token for a private repository).
- What we found. One card per detected field (framework, build type, port, start and build commands, output directory, runtime version, health path), each with a Why popover listing the signals behind it, and a confidence badge for the whole result. Below the cards, every value is an editable field, warnings come with a suggested fix, and the environment variables found in
.env.example,.env.sample, a compose file or the README are listed. Required ones must be filled in; values are never read from the repository, and secret-looking names get a masked field. - Review and deploy. The exact list of what will be created. One click creates the databases, the app and its connections, links the repository, and starts the first build. You land on the live deploy view with the step trace and logs.
sessionStorage (never tokens or env values), and the analysis is simply run again.
Explain it from the CLI
--output json returns the full result, including the detection object:
The same result comes from
POST /api/v1/git-sources/analyze (body: repo_url, optional ref and directory), which reuses your connected provider credentials. It reads only and persists nothing. The older POST /api/v1/imports/analyze returns the same payload.
What is detected
Next.js (App Router or Pages Router,standalone output, static export), Vite and Create React App single page apps, Astro (static or with the Node adapter), SvelteKit (static adapter or Node), Nuxt, Remix, Express, Fastify, NestJS, Django, FastAPI, Flask, Rails, Laravel, Go net/http, Rust axum, Spring Boot, plain static HTML, and any repository with a Dockerfile.
When signals conflict, the most specific framework wins: a Rails app with a package.json for its assets is Rails, and an Express server with a Dockerfile is built from the Dockerfile. A repository with several Dockerfiles uses the shallowest one and warns. A manifest that cannot be parsed falls back to matching its raw text, lowers the confidence, and warns.
Rust has no automatic build here yet. Detection still names it and warns; add a Dockerfile and it is built from that.
Monorepos
When the repository is a workspace (pnpm, npm, yarn, bun, Go workspaces, Nx or Turborepo), the What we found step lists its packages. Select the ones you want and press Create an app for each selected package. Each becomes its own app with the package directory as the build root and its watch paths applied, so a push only redeploys what it changed. You can also press Configure this one to take a single package through the normal steps. See Monorepos. Required environment variables are not asked for in the bulk action; add them on each app and redeploy.Worked examples
These are the fixtures undertest/fixtures. Output is abridged.
smoke/nextjs: a Dockerfile around Next.js
dogfood-nextjs: standalone output
Same as above, plus next.config.mjs sets output: 'standalone', so the server starts from .next/standalone. Port 3000, health /.
smoke/goapi: Go with a Dockerfile
go.sum; the warning only appears when go.mod has a require.
smoke/static: a Dockerfile serving static files
The Dockerfile decides: build type dockerfile, port 8080 from EXPOSE, no framework named. Remove the Dockerfile and the same files are detected as Static site, build type static, served on port 80.
monorepo: a workspace with no app at the root
failing-build: no recognisable framework
RUN step, which is what this fixture exists to show. See When a deploy fails.
Limits
- Analysis reads at most a few dozen small files per repository (
APP_WIZARD_MAX_FILES,APP_WIZARD_MAX_FILE_BYTES), so a huge tree can be partly read; a warning says so. - It works through the GitHub, GitLab and Gitea APIs. A host without a file API can still be connected by URL, but analysis answers that it cannot read it.
- Port and health guesses for Express and Fastify are conventions, not facts. Check them against the server code.