Deploy Without a Dockerfile: How Fly.io Builds Your App From Source

Deploy Without a Dockerfile: How Fly.io Builds Your App From Source

Author
Daniel Botha
Daniel Botha
Writer at Fly.io
Published

Deploying without a Dockerfile means handing a platform your source code and letting it produce the container image itself, by detecting the language or framework and generating the build steps a hand-written Dockerfile would otherwise spell out.

You have a Django app with a Postgres database and a Celery worker, or a Rails app, or an Axum service, and it runs on your laptop. Every deployment tutorial you open starts the same way: install Docker, write a Dockerfile, learn multi-stage builds. The app is finished, and the next step is learning a second tool before anyone can use it.

That step is optional. On Fly.io, fly launch reads the project, recognizes the framework, writes the Dockerfile and fly.toml into the repository, and deploys the app for the first time. The image still exists, because Fly Machines run images, but nobody had to write the file that builds it.


Do You Need Docker to Deploy a Web App?

No. Most modern platforms run a container image, but you do not need to write the Dockerfile that builds it or have Docker installed on your machine. A Dockerfile is a text file of instructions that Docker reads to assemble an image.[1] The image is what runs in production; the Dockerfile is one way of producing it, and a platform that understands your project can produce it another way.

Writing one by hand means making decisions that have nothing to do with the app. You choose a base image and pin its version. You order the steps so dependency installation is cached and a one-line code change does not reinstall every package. You split the build into stages so compilers and dev dependencies stay out of the final image. Then come the framework’s own build steps: Django collects static files, Rails precompiles assets, Phoenix builds a release. Each is a place for a first deploy to fail with an error that is about Docker, not about the code.

A platform that detects the framework already knows those steps, and the build it generates encodes the answers once instead of in every project.


What Replaces the Dockerfile?

Three mechanisms produce an image from source without a hand-written Dockerfile: buildpacks, framework-aware generators, and language toolchains that emit images directly. They differ mainly in whether you can see and edit what they produce.

Buildpacks

A buildpack inspects the source and decides how to build it. Cloud Native Buildpacks run in two phases: detect, where a buildpack checks whether it applies (a Python buildpack looks for a requirements.txt or setup.py), and build, where it installs the runtime and dependencies and produces the image.[2] Configuration happens through environment variables and project files rather than build instructions. Nothing is written into the repository, so the build lives in the buildpack, not in your project.

Framework-aware generators

A generator knows one framework well and writes a real Dockerfile into the project. Phoenix ships one: mix phx.gen.release --docker generates a release configuration and a Dockerfile for the app.[3] The output is an ordinary file you can read, commit, diff and change. That is the main difference from a buildpack: the build is visible in the repository, and the next person to deploy the app sees exactly what runs.

Toolchains that build images

Some language toolchains skip the Dockerfile at the source. The .NET SDK can publish an application straight to a container image with dotnet publish, configured through the project file.[4] This works well inside one ecosystem and does not help a polyglot team, because every language needs its own tool.


Why Is a Dockerfile Needed, Then?

A Dockerfile is needed when the build requires something detection cannot infer from the project files. Three cases account for most of it.

System packages. A route that converts video needs ffmpeg, an image pipeline needs libvips, a scraper needs a headless browser. None of these appear in requirements.txt or package.json, so a detector has no way to know about them. The Dockerfile is where the install line goes.

Layouts the detector does not recognize. A monorepo with the app in a subdirectory, two services sharing one repository, or a framework too new or too niche to have a detector. Detection works on conventions, and a project that breaks them needs to say how it builds.

Images built elsewhere. If CI already builds and tests an image, deploying that exact image is better than rebuilding it. The Dockerfile already exists, and the platform should take it as is. Fly.io deploys an existing Dockerfile or a prebuilt image unchanged, as the Docker hosting comparison covers.

A hand-written Dockerfile is also something to maintain. The base image tag it pins ages, the runtime version in it drifts from the one in the project, and a framework upgrade can change the build steps it copied from an old tutorial. Nobody notices until a deploy fails or an audit flags the image. A generated Dockerfile has the same drift, but the fix is to rerun the generator after an upgrade rather than to work out what changed by hand.

Even in the first two cases, a generator changes the starting point. Instead of writing a Dockerfile from a blank file, you edit the one that was generated, or rerun the generator with an option that adds the package.


Does Skipping the Dockerfile Mean Losing Dev/Prod Parity?

No, not when the build produces a file you can run yourself. Parity comes from the image: the same base, the same packages, the same build steps locally and in production. A generated Dockerfile committed to the repository gives exactly that, because docker build on a laptop runs the same instructions the platform runs.

The parity question is real for platforms that build from source inside their own builder and never show you the result. There, reproducing a production build locally means installing the platform’s tooling or guessing at its steps. The useful distinction is not Dockerfile versus no Dockerfile, but whether the build is a file in your repository or a process on someone else’s server.


Do I Need Both a Dockerfile and Docker Compose?

No. They do different jobs. A Dockerfile builds one image.[1] A Compose file describes a multi-container application as a set of services that run together, such as the app, a Postgres container and a Redis container on one machine.[5]

Compose is mostly a local development tool. In production on a platform, the pieces it wires together become separate things: the app runs from its image, the database is a managed service, and the platform’s own configuration replaces the Compose file. A project deploying to a platform that generates its build and provides the database needs neither file to get started, though a Compose file for local development does no harm.


Deploying Without a Dockerfile on Fly.io

On Fly.io the first deploy is one command, with prompts along the way, and every change after it is another:

fly launch   # first deploy: detect, generate, provision, deploy
fly deploy   # every change after that

Per the fly launch reference, it detects known project types, configures the app, builds the image, provisions resources such as Postgres, and deploys the app for the first time. The scanners are open source in flyctl’s scanner directory, and each framework gets its own generator or template. Every row below is read from the scanner file for that framework:

Framework How fly launch writes the Dockerfile What else it sets up
Rails Adds the dockerfile-rails gem and runs bin/rails generate dockerfile Offers Postgres when the app uses it
Django flyctl’s Django template, with Gunicorn and collectstatic at build time Offers Postgres when psycopg is a dependency, and runs migrations on each release
Laravel The dockerfile-laravel generator Reads the database and Redis settings from .env
Phoenix mix phx.gen.release --docker Offers Postgres and runs migrations on each release
Next.js, Remix, Nuxt, Astro, NestJS and other Node.js frameworks The @flydotio/dockerfile generator Offers Postgres when pg or a Prisma Postgres schema is present, and Redis when redis is
FastAPI, Flask flyctl’s Python template
Go, Rust (Axum, Actix Web, Rocket, Warp, Poem), .NET, Deno flyctl’s language templates

Sources: flyctl scanner/rails.go, django.go, laravel.go, phoenix.go, jsFramework.go, python.go, go.go, rust.go, dotnet.go and deno.go. Checked 2026-10-30.

For a Django project, scanner/django.go shows what the generated Dockerfile does: it pins the Python version found in the project, runs the app under Gunicorn (or Daphne for an ASGI app), and collects static files during the build, with a generated SECRET_KEY so collectstatic can run before the real secret exists. When psycopg is a dependency, fly launch also sets python manage.py migrate as the release command, so migrations run before each new version takes traffic.

The generated files are ordinary files in the repository. Edit the Dockerfile when the app needs a system package, or rerun the generator: the Node.js generator’s README documents --add ffmpeg, which rewrites the Dockerfile with that Debian package installed. If the project already has a Dockerfile, fly launch uses it and generates nothing. A project with neither can still build on Fly.io: the builders reference covers Cloud Native Buildpacks and deploying a prebuilt image.

Docker does not need to be installed. fly deploy builds on a remote builder by default; building with a local Docker daemon is opt-in with --local-only, per the flyctl deploy reference. A laptop with only flyctl and the source code can ship the app.

Postgres is offered during the launch. When a scanner finds a Postgres driver, fly launch asks whether to provision a database and attaches it to the app. For production, Managed Postgres is the path: fly mpg create makes the cluster and fly mpg attach connects it, adding DATABASE_URL to the app as a secret by default. Framework pages cover the rest for Django, Rails, Next.js and Rust.


Frequently Asked Questions

Is Docker still relevant in 2026?

Yes. Container images are still how most platforms run applications, and a Dockerfile is still the most direct way to control one. What changed is who writes it: on Fly.io, fly launch generates the Dockerfile for detected frameworks, and a hand-written one is only needed for builds detection cannot infer.

Can you use Docker to deploy?

Yes. A Dockerfile or a prebuilt image is a standard way to deploy an application. On Fly.io, fly launch uses an existing Dockerfile when the project has one, and an app can also deploy a prebuilt image from a registry without any build step.

Do I need Docker installed to deploy to Fly.io?

No. fly deploy builds the image on a remote builder by default, so only flyctl and the source code are needed on the machine you deploy from. Building with a local Docker daemon is an option, not a requirement.

Should I learn Docker before my first deployment?

No. A first deployment can go to a platform that generates the build. On Fly.io, fly launch writes the Dockerfile for detected frameworks, so the first deploy needs no Docker knowledge. Reading the generated Dockerfile afterwards is a practical way to learn what it does.

Can I edit the Dockerfile that fly launch generates?

Yes. fly launch writes the Dockerfile into the project directory as an ordinary file, and fly deploy builds the image on Fly.io from whatever that file says when it runs. Edit it to add system packages or change build steps, or rerun the framework’s generator with an option.

What happens if fly launch does not detect my framework?

If the project has a Dockerfile, fly launch offers to deploy from it. If not, it registers the app and writes a default fly.toml, and the build is set there before deploying: Fly.io can build with Cloud Native Buildpacks or deploy a prebuilt image instead of a Dockerfile.

Does fly launch write a Dockerfile for Rust and Go?

Yes. fly launch detects a Rust project from Cargo.toml, recognizes Axum, Actix Web, Rocket, Warp and Poem, and writes a Dockerfile from a template. Go projects get a generated Dockerfile the same way, and both deploy to Fly.io with fly deploy.

Does fly launch set up the database too?

Yes, when it detects a Postgres driver. fly launch offers to provision Postgres for Rails, Django, Phoenix and Node.js apps that use one and attaches it to the app as part of the same run on Fly.io.


Sources

  1. ^ Docker, “Dockerfile reference”. docs.docker.com/reference/dockerfile. Checked 2026-10-30.
  2. ^ Cloud Native Buildpacks, “What is a buildpack?”. buildpacks.io/docs/for-app-developers/concepts/buildpack. Checked 2026-10-30.
  3. ^ Phoenix, “mix phx.gen.release”. hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Release.html. Checked 2026-10-30.
  4. ^ Microsoft, “Containerize a .NET app with dotnet publish”. learn.microsoft.com/en-us/dotnet/core/containers/sdk-publish. Checked 2026-10-30.
  5. ^ Docker, “How Compose works”. docs.docker.com/compose/intro/compose-application-model. Checked 2026-10-30.

Skip the Dockerfile

fly launch detects your framework and writes the Dockerfile and fly.toml for you.

Sign up