Next.js apps don’t just run; they react.

Let’s get your Next.js app humming on Fly.io. Forget manual server setup; Fly.io handles the infrastructure, letting you focus on your code. We’re talking about a full-stack JavaScript app running on a global edge network.

Here’s a quick look at what a Fly.io deployment looks like under the hood for a Next.js app. Imagine a request comes in. Fly.io’s edge network receives it and routes it to the closest available VM running your Next.js app. Your app, likely using Node.js under the hood, spins up its server (often a custom server built by Next.js or by you) and renders the page. If it’s a dynamic route or API call, your app fetches data and returns the response. Fly.io then sends that response back through the edge network to the user. It’s fast, distributed, and surprisingly simple once you’ve got the pieces in place.

The Core Components

  1. Your Next.js App: This is the heart of it all. It needs to be built for production.
  2. fly.toml: This is Fly.io’s configuration file. It tells Fly.io how to run your app – what image to use, how to expose ports, resource limits, etc.
  3. Dockerfile: This describes how to build a container image for your Next.js app. Fly.io uses this to package your application and its dependencies.
  4. package.json: Standard Node.js dependency management, but crucially, it needs a build script and a start script.

Step-by-Step Deployment

First, ensure you have the Fly.io CLI installed and are logged in.

fly auth login

Now, navigate to your Next.js project’s root directory.

1. Prepare Your Next.js App

Make sure your package.json has the necessary scripts:

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start -p $PORT",
    "lint": "next lint"
  }
}

The -p $PORT in the start script is crucial. Fly.io injects the $PORT environment variable, telling your Node.js server which port to listen on.

2. Create a Dockerfile

This Dockerfile is optimized for Next.js. It uses a multi-stage build to keep the final image lean.

# syntax=docker/dockerfile:1

# ---- Base ----
FROM oven/bun:latest AS base
# You can also use nodejs or node with a specific version, e.g., FROM node:18-alpine
# Using Bun for potentially faster builds and smaller images, but Node.js is fully supported.

# ---- Builder ----
FROM base AS builder
WORKDIR /app

# Install dependencies using Bun (or npm/yarn if you prefer)
COPY package*.json .
RUN bun install --frozen-lockfile

# Copy the rest of your application code
COPY . .

# Build the Next.js application
RUN bun run build

# ---- Runner ----
FROM base AS runner
WORKDIR /app

# Copy built application from builder stage
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/public ./public

# Set the port environment variable (Fly.io injects this)
ENV PORT 8080

# Expose the port your app will listen on
EXPOSE ${PORT}

# Command to run the application
CMD ["bun", "run", "start"]

Why this Dockerfile works:

  • Multi-stage build: Reduces the final image size by not including build tools.
  • Dependency caching: Copies package.json and installs dependencies before copying the rest of the code, leveraging Docker’s layer caching.
  • bun install --frozen-lockfile (or npm ci): Ensures reproducible builds by using your lock file.
  • bun run build: Executes the Next.js build command.
  • ENV PORT 8080 and EXPOSE ${PORT}: Standard practice for containerized Node.js apps. Fly.io will override PORT with its assigned value.
  • CMD ["bun", "run", "start"]: The command to launch your Next.js production server.

3. Create fly.toml

In the root of your project, create a file named fly.toml:

app = "your-nextjs-app-name" # Replace with your desired app name on Fly.io
primary_region = "ord" # Or your preferred region, e.g., "lhr", "sin"

[build]
  dockerfile = "Dockerfile" # Point to your Dockerfile

[experimental]
  auto_assign_assets = true
  cmd = "bun run start" # This is often redundant if your Dockerfile has a CMD, but good to have

[http_service]
  internal_port = 8080 # Must match the EXPOSE port in your Dockerfile
  force_https = true
  auto_stop_machines = true
  auto_deploy = true

[[http_service.services]]
  protocol = "tcp"
  port = 80

Why this fly.toml is important:

  • app: Your unique identifier on Fly.io.
  • primary_region: Where your app’s primary instance will live.
  • [build]: Tells Fly.io to use Docker and specifies the Dockerfile to use.
  • [http_service]: Configures how your app handles HTTP traffic.
    • internal_port = 8080: This must match the port your application listens on inside the container (as defined by $PORT and EXPOSE in your Dockerfile).
    • force_https = true: Automatically redirects HTTP to HTTPS.
    • auto_stop_machines = true: If your app has no traffic for a while, Fly.io will stop its machines to save costs.
    • auto_deploy = true: Automatically deploys new versions when you push code.
  • [[http_service.services]]: Defines the public-facing port. port = 80 means it listens for standard HTTP requests. Fly.io handles TLS termination, so you don’t need to manage certificates.

4. Deploy

Now, run the deploy command from your project’s root:

fly deploy

Fly.io will build your Docker image, push it to its registry, and then deploy it to a VM. This might take a few minutes.

5. Access Your App

Once the deployment is complete, Fly.io will give you a URL. You can also find it with:

fly apps show

The output will include your app’s URL, typically something like your-nextjs-app-name.fly.dev.

Handling Environment Variables

For sensitive information or configuration specific to your production environment (like API keys), use flyctl secrets:

fly secrets set MY_API_KEY=your_secret_key --app your-nextjs-app-name

Your Next.js app can access these secrets as environment variables. In next.config.js or directly in your server code, you’d reference process.env.MY_API_KEY.

The Next Step: Performance and Edge Functions

Once your app is deployed, you’ll likely want to optimize it further. Consider exploring Fly.io’s Edge Functions for tasks like A/B testing, personalization, or request rewriting directly at the edge, before they even hit your Next.js application.

Want structured learning?

Take the full Fly-io course →