GetFlowingGetFlowing
Build

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.

StackRecognised byHow it is built and started
PHPcomposer.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.
RubyGemfilebundle 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.
Gogo.modThe 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.
RustCargo.tomlcargo build --release, running the binary its one [[bin]] or its package names.
Javapom.xml, build.gradle.kts or build.gradlePackaged 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.
.NETA .csproj or .fsproj at the root, or one web project in src/ beside a solution filedotnet publish, run on ASP.NET Core.
Elixirmix.exsmix release for production. A Phoenix app's assets are built with mix assets.deploy where mix.exs defines it, and its server is started.
Gleamgleam.tomlgleam export erlang-shipment, run on Erlang.
Denodeno.json or deno.jsonc, or a deno.lock with no package.jsonInstalled, built with its build task, and started with its start task or its main module.
Pythonrequirements.txt, pyproject.toml, Pipfile, setup.py, manage.py, or the lockfile of uv, Poetry or PipenvInstalled 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.
Nodepackage.jsonInstalled 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 NodeVite, 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 EleventyBuilt with Node, then served as files.
Static siteAn index.html with no server code or .env file beside itServed 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.

StackWhere it reads the versionVersionsDefault
Node.nvmrc, .node-version, .tool-versions, Volta, or engines in package.json20, 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-python3.10, 3.11, 3.12, 3.13 and 3.143.13
GoThe toolchain or go line in go.mod1.21 to 1.27. Others build on the nearest of these.1.27
Rustrust-toolchain or rust-toolchain.tomlAny release1.98
Javapom.xml or the Gradle build8, 11, 17, 21 and 25. Another runs on the nearest of these above it, or on the newest.21
.NETTargetFramework in the project8.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.lock3.1, 3.2, 3.3 and 3.43.4
PHPThe php version composer.json requires8.1, 8.2, 8.3 and 8.48.4
ElixirNot readElixir 1.20 on Erlang/OTP 28The same
GleamNot readGleam 1.19.2 on Erlang/OTP 28The same
DenoNot readDeno 2.9.8The 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's web: line, else the start:prod, start or serve script in package.json. A script that only runs a development server is skipped. With none, GetFlowing tries the framework's own server (Next.js, Nuxt, SvelteKit with adapter-node, Astro with @astrojs/node, NestJS), then the main file in package.json, then server.js, index.js, app.js or main.js.
  • Python: a Procfile's web: 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.py or server.py, which must then read PORT itself.
  • Ruby: a Procfile's web: line, else rails server for Rails, else Puma or rackup on config.ru.
  • Deno: a Procfile's web: line, else the start task in deno.json (its preview task where start only watches for changes), else its main module, such as main.ts, server.ts or app.ts, with every permission. An app that serves with Deno.serve or deno serve answers on the port in PORT whatever 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:

PrefixFor
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:

AppVariable
LaravelAPP_KEY
RailsRAILS_MASTER_KEY or SECRET_KEY_BASE
PhoenixSECRET_KEY_BASE, where config/runtime.exs stops without it
DjangoThe 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.exs reads PHX_HOST, every deploy sets it to the app's address, unless the app's variables set it.
  • An app that uses PostgreSQL through Ecto (postgrex in mix.lock) is given a managed database, under the variable config/runtime.exs reads. 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_ROOT is set.
  • ALLOWED_HOSTS: the health check asks the app by its own address, which Django refuses unless ALLOWED_HOSTS names it. Where the settings read ALLOWED_HOSTS from a variable as a list, such as os.environ.get("ALLOWED_HOSTS", "").split(","), GetFlowing adds that address when it starts the app (a Procfile that 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's check_app says 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 managerWhat is installed
pnpmThe app, the packages it depends on, and the root
npmThe app and the root
Yarn 4 and laterThe 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 BunThe whole workspace
TurborepoThe 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: or link: path outside the repository.
  • It takes packages from a workspace (workspace: or catalog: 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.json has no name, 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 appWhyWhat to do
SvelteKit on adapter-autoadapter-auto builds only for Vercel, Netlify and CloudflareUse @sveltejs/adapter-node, or adapter-static for a static site
Astro with another host's adapterThe adapter builds for that hostUse @astrojs/node, or no adapter for a static site
Django or Flask with no production serverNothing listed can serve it in productionAdd gunicorn to your dependencies
FastAPI with no uvicornNothing listed can serve itAdd uvicorn, or fastapi[standard]
FastAPI or Flask whose app is not foundNo app = FastAPI(...), app = Flask(...) or create_app() in main.py, app.py or the other files such an app starts fromAdd a Procfile with a web: line
Django whose manage.py names no settings moduleThe project to serve is not knownAdd a Procfile with a web: line
Python with nothing to startNo Procfile, web framework or main.pyAdd a Procfile with a web: line
Node with nothing to startNo start script, and no server.js, index.js, app.js or main.jsAdd 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 namesCommit the lockfile, or build it as part of its workspace
A static site framework with no build scriptIts files cannot be madeAdd a build script
A workspace's app that cannot be built as oneSee Apps in a monorepoChange the workspace, or add a Dockerfile
Gradle without its wrapperWhich Gradle builds it is not knownCommit gradlew and its gradle folder
PHP asking for a version not listed aboveThere is no image for itAsk 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 appWhich one serves the app is not knownAdd a Dockerfile that builds the right one
Go with no main package at its root or in cmd/There is nothing to runAdd a Dockerfile
A Cargo workspaceWhich crate serves the app is not knownAdd a Dockerfile
.NET with several web projects, or several project files at the rootWhich one is the app is not knownAdd a Dockerfile
An Elixir umbrella project whose mix.exs names no releaseWhich of its apps serves the app is not knownName the release in mix.exs
Gleam on the JavaScript targetOnly Gleam on Erlang is built without a DockerfileAdd a Dockerfile
Gleam taking a package from a path outside its folderThe build does not have that pathAdd a Dockerfile
Deno with nothing to startNo Procfile, start task in deno.json or main moduleAdd a start task
An index.html beside server code or an .env fileServing the folder as a static site would publish themMove the site into its own folder, or add a Dockerfile
None of the files aboveNo stack is recognisedAdd 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.

On this page