Fly.io is a great place to run fullstack applications. For most programming languages, there is a defacto default fullstack framework. For Ruby, there is Rails. For Elixir, there is Phoenix. For PHP there is Laravel. For Python, there is Django.
The result is predictable. Fly.io has a number of community contributed templates for a small number of Node frameworks. Some have had more attention than others.
It is time to clean up the mess.
package.json Enters the Chat
The key sentence in the preceding section starts with If you don't know where to look. The right place to start is
package.json. It tells you what dependencies need to be installed. For most frameworks, it tells you how to start the web server. And if there is a build step. And if there are any development dependencies that may be needed to run the build, and removed prior to deployment.
Given this knowledge, a baseline Dockerfile can be built for any framework that follows these conventions. Handling different package managers can be accomplished by looking for
pnpm-lock.yaml files. TypeScript is a devDependency and handled by the build step. While Deno projects don't typically have
package.json files, some bun projects do.
The dockerfile-node project endeavors to do exactly that:
This will create (or replace!) your existing Dockerfile, as well as ensure that you have a
.dockerignore file, and optionally may create a
docker-entrypoint script. You can run with this Dockerfile locally, or use it to deploy on your favorite cloud provider. For Fly.io, you would get started by running:
fly launch --dockerfile Dockerfile
--dockerfile parameter is needed to tell
fly launch to use your Dockerfile rather than trying to generate a new one.
Of course, if you prefer to run your application on Google Cloud Run, Amazon ECS, MRSK, or even locally, you are welcome to do so.
Devils in the Details
Not all frameworks are alike.
Some will, by default, start servers that only process requests that come from the localhost. That, of course, is entirely unsatisfactory.
Some require extra steps, for example applications that make use of Prisma.
One (and I won't mention the name) actually lists the package needed to run the production server as a development only dependency.
Fortunately, ejs templates can include
if statements and/or make use of computed variables that customize the Dockerfiles produced.
As a starter set, I've got templates working for the following frameworks: Express, Fastify, Gatsby, Nest, Next, Nuxt, and Remix. At the moment, I've been focusing on breadth vs depth, so what I have working may not be able to handle much more than the splash screen, but my experience is that getting that far is often the hardest part, after that point you have all the scaffolding in place and can focus on any specific issue that may come up.
Those are the successes so far. Here's a list of frameworks that are still being worked on, along with the current blocking issue:
- tRPC: Access to the Postgres database is required during the build step. Worst case, we do the build step during the deployment of the server, but that is suboptimal for cases where multiple servers are started.
- Strapi: Needs to set secrets for JWT, session. This isn't a problem, and already is solved for Remix deployment for fly, but at the moment goes beyond what a Dockerfile generator can do by itself.
- RedwoodJS: No scripts, recommends nginx. Fly.io already has a template for Redwood, so it presumably is just a matter of work to figure out how to fit the steps required into the general purpose template. It may make sense to either encourage Redwood to add scripts to their
package.json, or to add them during the dockerfile generation. If not,
ifstatements can be used to generate Redwood-specific steps rather than generic ones.
- SvelteKit: attempting to deploy results in
Could not detect a supported production environment. Again, just appears to be a matter of work to add a new production environment.
- KeystoneJS: at build time, I'm seeing
✘ [ERROR] Could not resolve "./keystone". Works fine on development machine, so I probably just missed a step.
In the fullness of time, these will be picked off one by one. This code is all open source, so everybody with an interest in a particular framework can contribute via issues and pull requests. Interest and participation will definitely affect prioritization of this work.
Once this script has a little bit of exposure to real world usage, it will replace the existing flyctl scanners, much in the way that dockerfile-rails is the basis for the Dockerfiles produced for Rails applications with Fly.io. At which point, usage will be as simple as
Integration with fly launch will also enable thing like setting of secrets, defining volumes, launching of databases, and defining health checks as part of the workflow.
This package will also be designed to be re-run and accept arguments which will customize the Dockerfile produced. Peruse the usage for dockerfile-rails to see examples of the types of customizations possible. Some highlights:
--cache- use build caching to speed up builds
--swap=n- allocate swap space enabling running of larger applications on memory constrained VMs.
--compose- generate a
The scanner will also be able to do things like detect the inclusion of
puppeteer and automatically install and configure Chrome/Chromium. This is already being done for Rails applications today.
Another thing already being done for Rails applications is to run the web server as a non-root user for security reasons. Repeating this for Node.js will require knowledge of what files the application is expected to write to and which are expected to be read-only. This knowledge is necessarily framework specific, and may not be possible for minimal and general purpose frameworks like express.
If you have questions, comments, or concerns, let us know!
If they are even vaguely Fly.io related, feel free to use our community forum. Otherwise, start a discussion on GitHub.
And to those that wish to contribute, perhaps to make support for their favorite framework(s) better.... let's do this!