Deploy an app without a Dockerfile
Which stacks GetFlowing builds with no Dockerfile, the versions and start commands it picks, the variables it asks for, and what to do when it cannot.
Most apps need no Dockerfile. If yours has none, GetFlowing reads its files, works out the stack, and writes a Dockerfile for it when it builds. It never adds the file to your repository.
The Dockerfile it wrote is printed at the start of the build log. When the deploy goes live, GetFlowing says what it built the app as, for example:
GetFlowing built it as a Next.js app (pnpm, Node 24), with a Dockerfile it wrote as the repository has none of its own; add one to the repository to change how it is built.
A connected AI assistant can find out before anything is built: its check_app says what GetFlowing would build the app as, or why it cannot. To change how your app is built, add your own Dockerfile. See Take over with your own Dockerfile.
The stacks it recognises
The first stack that matches your app is used, in the order below. A PHP, Ruby, Go, Rust, Java, .NET, Elixir, Gleam or Deno app can also have a package.json, for its front end or its npm packages, and it is still built as its main stack. A Python app that lists Django, FastAPI, Flask, Starlette, gunicorn or uvicorn is built as Python even if it also has a package.json.
| Stack | Recognised by | How it is built and started |
|---|---|---|
| PHP | composer.json, or an index.php at the root or in public/ | composer install without development packages, served by Apache with the PHP extensions Laravel and Symfony need. Where public/ has the index.php, it is the site's root, and a path with no file goes to that index.php. A front end with a build script in package.json is built with Node. |
| Ruby | Gemfile | bundle install without the development and test gems. A Rails app's front end is built with Node where it has a build script, its assets are precompiled where it uses Propshaft or Sprockets, and it runs rails server. Another app runs Puma, or rackup on config.ru. |
| Go | go.mod | The main package at the root, or the one folder in cmd/, built as one program. The module's files are kept beside it, for the templates and static files it reads. |
| Rust | Cargo.toml | cargo build --release, running the binary its one [[bin]] or its package names. |
| Java | pom.xml, build.gradle.kts or build.gradle | Packaged with Maven or the Gradle wrapper, without tests, then the largest jar it made is run, or Quarkus's quarkus-app. Java may use three quarters of the app's memory. |
| .NET | A .csproj or .fsproj at the root, or one web project in src/ beside a solution file | dotnet publish, run on ASP.NET Core. |
| Elixir | mix.exs | mix release for production. A Phoenix app's assets are built with mix assets.deploy where mix.exs defines it, and its server is started. |
| Gleam | gleam.toml | gleam export erlang-shipment, run on Erlang. |
| Deno | deno.json or deno.jsonc, or a deno.lock with no package.json | Installed, built with its build task, and started with its start task or its main module. |
| Python | requirements.txt, pyproject.toml, Pipfile, setup.py, manage.py, or the lockfile of uv, Poetry or Pipenv | Installed with uv, Poetry or Pipenv from their lockfiles, otherwise pip. Django and Flask run on gunicorn, FastAPI on uvicorn, where the app lists that server. |
| Node | package.json | Installed with npm, pnpm, Yarn or Bun, built with your build script, started with your start script or the framework's own server. |
| Static site built with Node | Vite, Create React App, Angular without server rendering, Gatsby, Vue CLI, Astro without a server adapter, SvelteKit with adapter-static, Next.js with output: 'export', Docusaurus or Eleventy | Built with Node, then served as files. |
| Static site | An index.html with no server code or .env file beside it | Served as it is. |
Every app it builds runs as a user other than root.
Static sites
A static site is served as files. A path is served as its file, its .html page or its folder, and anything else gets index.html, so a single-page app's routes work. Files whose names start with .env, .git or .ht, and .npmrc, are never served.
Choosing versions
GetFlowing reads the version your app asks for, and otherwise uses its default.
| Stack | Where it reads the version | Versions | Default |
|---|---|---|---|
| Node | .nvmrc, .node-version, .tool-versions, Volta, or engines in package.json | 20, 22, 24 and 26. An older one runs on the nearest of these above it. | 24 |
| Python | .python-version, runtime.txt, the Pipfile, or requires-python | 3.10, 3.11, 3.12, 3.13 and 3.14 | 3.13 |
| Go | The toolchain or go line in go.mod | 1.21 to 1.27. Others build on the nearest of these. | 1.27 |
| Rust | rust-toolchain or rust-toolchain.toml | Any release | 1.98 |
| Java | pom.xml or the Gradle build | 8, 11, 17, 21 and 25. Another runs on the nearest of these above it, or on the newest. | 21 |
| .NET | TargetFramework in the project | 8.0, 9.0 and 10.0. An older one runs on 10.0. | 10.0 |
| Ruby | .ruby-version, or the ruby line in the Gemfile or Gemfile.lock | 3.1, 3.2, 3.3 and 3.4 | 3.4 |
| PHP | The php version composer.json requires | 8.1, 8.2, 8.3 and 8.4 | 8.4 |
| Elixir | Not read | Elixir 1.20 on Erlang/OTP 28 | The same |
| Gleam | Not read | Gleam 1.19.2 on Erlang/OTP 28 | The same |
| Deno | Not read | Deno 2.9.8 | The same |
The package manager comes from packageManager in package.json, or from the lockfile you commit. Commit your lockfile: the install is held to it, so the versions you tested are the versions that are built.
Choosing the start command
- Node: a
Procfile'sweb:line, else thestart:prod,startorservescript inpackage.json. A script that only runs a development server is skipped. With none, GetFlowing tries the framework's own server (Next.js, Nuxt, SvelteKit withadapter-node, Astro with@astrojs/node, NestJS), then themainfile inpackage.json, thenserver.js,index.js,app.jsormain.js. - Python: a
Procfile'sweb:line, else the framework's production server: gunicorn for Django and Flask (uvicorn for a Django app served over ASGI, waitress for Flask), uvicorn for FastAPI. With no framework,python main.py,app.pyorserver.py, which must then readPORTitself. - Ruby: a
Procfile'sweb:line, elserails serverfor Rails, else Puma or rackup onconfig.ru. - Deno: a
Procfile'sweb:line, else thestarttask indeno.json(itspreviewtask wherestartonly watches for changes), else its main module, such asmain.ts,server.tsorapp.ts, with every permission. An app that serves withDeno.serveordeno serveanswers on the port inPORTwhatever address it names, and a module that exports a fetch handler is served. - PHP: Apache, on the port in
PORT. - Go, Rust, Java, .NET, Elixir and Gleam: the program the build made.
Whatever starts your app must listen on 0.0.0.0 on the port in PORT. See What every app needs.
Variables the build reads
A front end reads some variables when it is built, not when it starts, and puts their values into the pages every visitor downloads. When GetFlowing writes the Dockerfile, its build is given your app's variables with the prefix the app's framework reads:
| Prefix | For |
|---|---|
NEXT_PUBLIC_ | Next.js |
VITE_ | Vite, and a Laravel, Rails or Deno front end built with it |
REACT_APP_ | Create React App |
PUBLIC_ | Astro and SvelteKit |
GATSBY_ | Gatsby |
VUE_APP_ | Vue CLI |
EXPO_PUBLIC_ | Expo |
FRESH_PUBLIC_ | Fresh, when its build task runs |
- Only variables not marked secret are given. When you add one, untick Store as a secret.
- A variable marked secret is never given to the build, whatever its prefix. The deploy names any it left out:
NEXT_PUBLIC_API_URL is marked secret, so the build was not given it: a variable with a public prefix is put into the pages every visitor downloads. If it is meant for the browser, unmark it as secret; if not, rename it without the prefix.
- The values are never written into the image's history or the build log. The log names the variables only.
- A changed value reaches the app at its next deploy, which builds it again.
- If these variables come to more than 16 KB compressed, the deploy is refused before anything is built.
With a Dockerfile of your own, the build is given none of your variables. See Variables and secrets.
What an app cannot start without
Some frameworks cannot start without a secret that only you can give. GetFlowing finds them from the app's code and asks for them before the deploy:
| App | Variable |
|---|---|
| Laravel | APP_KEY |
| Rails | RAILS_MASTER_KEY or SECRET_KEY_BASE |
| Phoenix | SECRET_KEY_BASE, where config/runtime.exs stops without it |
| Django | The variables its settings read SECRET_KEY and ALLOWED_HOSTS from, where they have no default it can start with |
If the app's variables do not have one, the deploy is refused before anything is built. The message names each variable and what it is for, for example:
GetFlowing builds this as a Laravel app (PHP 8.4), which cannot start without APP_KEY, the key Laravel encrypts sessions and cookies with: run php artisan key:generate --show on your computer and set what it prints, marked secret. Set it in the app's Variables, then deploy again.
Set each one in the app's Settings, under Variables, a key marked secret, then deploy again. GetFlowing compares only the names: it never reads, makes or logs a value. The app's chat says the same before it asks for any approval, and a connected assistant's check_app lists them.
Framework notes
Phoenix
- Where
config/runtime.exsreadsPHX_HOST, every deploy sets it to the app's address, unless the app's variables set it. - An app that uses PostgreSQL through Ecto (
postgrexinmix.lock) is given a managed database, under the variableconfig/runtime.exsreads. See Databases. - Its server is started, and it runs with no Erlang distribution.
Django
- Static files are collected when the app is built, where WhiteNoise serves them and
STATIC_ROOTis set. ALLOWED_HOSTS: the health check asks the app by its own address, which Django refuses unlessALLOWED_HOSTSnames it. Where the settings readALLOWED_HOSTSfrom a variable as a list, such asos.environ.get("ALLOWED_HOSTS", "").split(","), GetFlowing adds that address when it starts the app (aProcfilethat starts it is left as it is). Where the hosts are written into the settings, or read as one host, and do not include"*", the health check fails, and a connected assistant'scheck_appsays so. Read them from a variable as a list instead.
Laravel and Rails
Laravel's APP_KEY, and Rails's RAILS_MASTER_KEY or SECRET_KEY_BASE, are asked for before the deploy. A front end with a build script in package.json is built with Node, and given the variables its build reads.
Apps in a monorepo
Importing several apps from one repository is not on for every workspace yet. Until it is on for yours, an import builds from the root of the repository.
A Node app in a folder of its repository, where a workspace at the repository's root names that folder (in the workspaces list of the root's package.json, or the packages of pnpm-workspace.yaml), is built from the repository's root, with the lockfile committed there. The packages it depends on are built first.
| Package manager | What is installed |
|---|---|
| pnpm | The app, the packages it depends on, and the root |
| npm | The app and the root |
| Yarn 4 and later | The app and the packages it depends on. The build stops if yarn.lock does not match the workspace's package.json files. |
| Yarn 1 to 3, and Bun | The whole workspace |
| Turborepo | The app and what it depends on, pruned by Turborepo, where the root lists turbo, names its packageManager, and its lockfile holds Turborepo 2 or later |
A Node app with a lockfile of its own, which takes nothing from a workspace, is built from its folder alone. So is an app of any other stack in a folder.
A workspace's app is refused when:
- There is no lockfile committed at the repository's root.
- It takes a package from a
file:orlink:path outside the repository. - It takes packages from a workspace (
workspace:orcatalog:versions, or a path outside its folder), and no workspace at the root names its folder. - It takes a package with a
workspace:version, and no package of that name is in the workspace's folders. - Its
package.jsonhas noname, and it is built with pnpm, Yarn 2 or later, or Turborepo, which pick it by its name.
Apps it cannot build without a Dockerfile
For these apps GetFlowing does not guess. It says why before it builds, and you add a Dockerfile or change the app.
| Your app | Why | What to do |
|---|---|---|
SvelteKit on adapter-auto | adapter-auto builds only for Vercel, Netlify and Cloudflare | Use @sveltejs/adapter-node, or adapter-static for a static site |
| Astro with another host's adapter | The adapter builds for that host | Use @astrojs/node, or no adapter for a static site |
| Django or Flask with no production server | Nothing listed can serve it in production | Add gunicorn to your dependencies |
| FastAPI with no uvicorn | Nothing listed can serve it | Add uvicorn, or fastapi[standard] |
| FastAPI or Flask whose app is not found | No app = FastAPI(...), app = Flask(...) or create_app() in main.py, app.py or the other files such an app starts from | Add a Procfile with a web: line |
Django whose manage.py names no settings module | The project to serve is not known | Add a Procfile with a web: line |
| Python with nothing to start | No Procfile, web framework or main.py | Add a Procfile with a web: line |
| Node with nothing to start | No start script, and no server.js, index.js, app.js or main.js | Add a start script |
Node with no lockfile, taking some packages at any version ("*") | A build of its folder alone would install whatever the public registry has under those names | Commit the lockfile, or build it as part of its workspace |
| A static site framework with no build script | Its files cannot be made | Add a build script |
| A workspace's app that cannot be built as one | See Apps in a monorepo | Change the workspace, or add a Dockerfile |
| Gradle without its wrapper | Which Gradle builds it is not known | Commit gradlew and its gradle folder |
| PHP asking for a version not listed above | There is no image for it | Ask for one of 8.1, 8.2, 8.3 and 8.4 |
Go with several commands in cmd/, none named after the module or server, api, web or app | Which one serves the app is not known | Add a Dockerfile that builds the right one |
Go with no main package at its root or in cmd/ | There is nothing to run | Add a Dockerfile |
| A Cargo workspace | Which crate serves the app is not known | Add a Dockerfile |
| .NET with several web projects, or several project files at the root | Which one is the app is not known | Add a Dockerfile |
An Elixir umbrella project whose mix.exs names no release | Which of its apps serves the app is not known | Name the release in mix.exs |
| Gleam on the JavaScript target | Only Gleam on Erlang is built without a Dockerfile | Add a Dockerfile |
| Gleam taking a package from a path outside its folder | The build does not have that path | Add a Dockerfile |
| Deno with nothing to start | No Procfile, start task in deno.json or main module | Add a start task |
An index.html beside server code or an .env file | Serving the folder as a static site would publish them | Move the site into its own folder, or add a Dockerfile |
| None of the files above | No stack is recognised | Add a Dockerfile |
Not covered yet
- Stacks not in the table above. Add a Dockerfile for them.
- Other Elixir, Gleam and Deno releases. Each is built on the one release in Choosing versions. An app that needs a newer one fails its build, and the build log says why.
- Installing one app of a Yarn 1 to 3 or Bun workspace. The whole workspace is installed.
- A Django app whose hosts nothing can be added to. It fails its health check. See Django.
Take over with your own Dockerfile
To change how your app is built, add a Dockerfile where GetFlowing looks for one. GetFlowing then builds from yours and writes none. The one it wrote is at the start of the build log, so you can start from that. See Deploy an app with your own Dockerfile.
A Dockerfile of your own is given none of your app's variables while it builds, so a front end that reads them at build time needs another way to get them. See What it does not support.
Your own Dockerfile
How GetFlowing builds an app from the Dockerfile in its repository, where it looks for one, what to check first, and what the build does not support.
App settings
Rename an app, choose the branch it deploys from, see where its code lives, and take an app offline, hand it to someone else or delete it.