The Best Way to Deploy Next.js to Fly.io
By Manthan Mallikarjun, Published 3 months ago
Fly.io is an amazing service which allows you to deploy container all across the globe with ease. The have abstracted away a lot of the complexity, to the point where you give them a Dockerfile and they do the rest.
The problem
While Fly works really well for traditional API services, Next.js has a couple of quirks that makes it harder to deploy. Typically your Next.js app will need some public environment variables (prefixed with NEXT_PUBLIC
) as well as some private environment variables during the build command.
Fly (rightfully) prefers the official way to provide secrets into a Dockerfile which is more secure than blindly injecting secrets into the build environment. Typically that will look something like this:
Mount the secrets:
RUN --mount=type=secret,id=MY_SUPER_SECRET \
MY_SUPER_SECRET="$(cat /run/secrets/MY_SUPER_SECRET)" some_command \
&& more_commands_maybe
and then pass it during deployment:
fly deploy \
--build-secret MY_SUPER_SECRET=some_value
In an application where you have many secrets, this process becomes cumbersome and error-prone. We found a way to make this process easier and more hands-off.
NOTE: Fly.io does have this CLI solution to make things easier, but it still feels like a cumbersome process
The solution
We have many applications with many secrets. Trying to remember to update the Dockerfile, Fly.io's runtime secrets, and GitHub actions was a nightmare. We found a way to make this process easier and more hands-off using a couple of tools.
Overview
Our solution involves the following steps:
- Store the env vars in a central location. Sync it to Fly.io and GitHub actions
- Build the Next.js application inside of GitHub actions (outside of Fly.io)
- Copy the built files and deploy that container to Fly.io
Infisical
Infisical is the key to our solution. It allows us to manage our secrets in a single place and then inject them into our Dockerfile, Fly, and GitHub actions. It is open source and free to self host or you can use their hosted solution.
To get started, we set up Infisical, created our project, and added our production secrets. Then we set up the following syncs to Fly.io and GitHub actions:
Note that for the GitHub integration we specifically use the Repository Environment
scope.
GitHub actions
The next step is to build the Next.js application inside of GitHub actions. We use the following GitHub action to build the application:
deploy:
runs-on: ubuntu-latest
environment: production # IMPORTANT, you need this to absorb the synced variables
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
cache: 'npm'
- uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }}
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
- run: npm ci
- uses: koyashiro/[email protected]
with:
secrets: ${{ toJSON(secrets) }}
- run: npm run build
- uses: docker/setup-buildx-action@v3
- uses: superfly/flyctl-actions/setup-flyctl@master
- run: flyctl deploy --local-only
Dockerfile
Finally, you'll need the Dockerfile to collect the built files and deploy it to Fly.io. First you'll need to make a change in the next.config.js
file.
module.exports = {
output: 'standalone',
};
This tells Next.js that you'll be running the app in a server as opposed to a serverless environment.
Then you can use the following Dockerfile:
FROM node:lts-alpine as base
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY public* ./public
RUN mkdir .next
RUN chown nextjs:nodejs .next
COPY --chown=nextjs:nodejs .next/standalone ./
COPY --chown=nextjs:nodejs .next/static ./.next/static
USER nextjs
ENV PORT 3000
EXPOSE 3000
CMD HOSTNAME="0.0.0.0" node server.js
Deploy
Commit and push your changes and watch as GitHub actions build your Next.js application and deploy it to Fly.io. You can now manage your secrets in a single place and have them automatically synced to Fly.io and GitHub actions.