Skip to content

Deploy a Next.js App

How to Deploy a Next.js App with a custom domain, HTTPS, WAF and Rate Limiter

In this guide, I will show how to deploy a Next.js application on a VPS, cloud, or dedicated server using Docker and Caddy. Whether you’re running your application on AWS, DigitalOcean, Hetzner, Scaleway or a dedicated physical server, the steps remain largely the same.

Setting Up the Server

Create a new server on your cloud provider or local machine with a public IP address, SSH access from your development machine, and a fresh Ubuntu 22.04 installation. Ensure that ports 22, 80, and 443 are open. (Some cloud providers, like AWS, close ports by default; you’ll need to open them before deployment.)

If you’re looking for a new cloud provider, consider Hetzner, DigitalOcean, or OVH. These providers typically have ports open by default. OVH also offers affordable domain registration and free DNS management via API.

Deploy with One Command

If you don’t want to deploy manually or don’t have time to set up a deployment pipeline, you can deploy your Next.js application to any cloud provider or private server with a single command from your local repository using TurboCloud. (No need for GitHub, Bitbucket, or even Git!) You can also deploy directly from GitHub or Bitbucket repositories.

Prepare a Next.js App for Deployment

If you deploy with TurboCloud, it will check for the presence of a Dockerfile in the project's root directory. If no Dockerfile is found, TurboCloud will attempt to generate one automatically using Nixpacks. (See here for details on how Nixpacks generates Dockerfiles for Next.js apps.) However, we recommend creating a Dockerfile manually and placing it in the project's root folder for better control:

  • Create a file named Dockerfile in the root of the project.
  • Add the following content to Dockerfile:
# syntax=docker.io/docker/dockerfile:1

FROM node:18-alpine AS base

# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app

# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./
RUN \
  if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
  elif [ -f package-lock.json ]; then npm ci; \
  elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
  else echo "Lockfile not found." && exit 1; \
  fi


# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED=1

RUN \
  if [ -f yarn.lock ]; then yarn run build; \
  elif [ -f package-lock.json ]; then npm run build; \
  elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
  else echo "Lockfile not found." && exit 1; \
  fi

# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app

ENV NODE_ENV=production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED=1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT=3000

CMD ["node", "server.js"]
  • Add the following to the next.config.js file:
// next.config.js
module.exports = {
  // ... rest of the configuration.
  output: "standalone",
};

Deploy from the Local Repository

  • On your development machine, navigate to the folder containing the project's code and run the deployment command (replace server_ip with the actual public IP of your server and service_port with the port number your app uses):
cd my_project
curl https://turbocloud.dev/deploy | bash -s -- -i server_ip -p 3000
  • Wait until the deployment script displays the URL of the deployed Next.js application.
  • Full documentation for all available parameters and detailed instructions can be found at turbocloud.dev/docs.

Deploy from GitHub or Bitbucket

  • SSH into your server (replace server_ip with the actual public IP of your server):
ssh root@server_ip
  • Run the setup command:
curl https://turbocloud.dev/setup | bash -s
  • Once installation is complete, start the TurboCloud TUI (Terminal User Interface), which functions similarly to standard applications on macOS, Linux, and other operating systems but can operate on servers without displays. Use the TUI to set up deployments with interactive guides:
turbocloud

Step-by-Step guide

1. Prerequisites

Before getting started, ensure you have the following:

  • A VPS with Ubuntu 22.04, SSH access and public IP.
  • A registered domain name.
  • Docker installed on your development machine.

2. Setting Up the VPS

Start by securing and preparing your server:

  • Create a new Ubuntu 22.04 instance on your cloud provider or local machine.
  • SSH into your server:
ssh user@your_server_ip
  • Update your server’s packages:
sudo apt update
sudo apt upgrade -y
  • Install Docker:
sudo apt install docker.io -y
  • Install Docker Compose:
sudo apt install docker-compose -y
  • Start Docker service:
sudo systemctl enable docker
sudo systemctl start docker
  • Check Docker version:
docker --version

Now that Docker is installed and running on your server, proceed to the next step of creating your Next.js application.

3. Creating a Simple Next.js Application

Let’s create a basic Next.js app locally before deploying it to the VPS.

Install Node.js and Create the App

npx create-next-app@latest my-next-app
cd my-next-app
  • Add the following to the next.config.js file:
// next.config.js
module.exports = {
  // ... rest of the configuration.
  output: "standalone",
};

- Verify the app runs locally:
```bash
npm run dev

Your app should be running at http://localhost:3000. For this example, the default Next.js boilerplate will suffice.

Build the Application

Prepare your app for production by running:

npm run build
npm run start

4. Dockerizing the Next.js Application

To containerize the Next.js app, we’ll create a Dockerfile that sets up the environment and runs the application.

Create a Dockerfile

Inside your project directory, create a Dockerfile:

# syntax=docker.io/docker/dockerfile:1

FROM node:18-alpine AS base

# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app

# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./
RUN \
  if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
  elif [ -f package-lock.json ]; then npm ci; \
  elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
  else echo "Lockfile not found." && exit 1; \
  fi


# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED=1

RUN \
  if [ -f yarn.lock ]; then yarn run build; \
  elif [ -f package-lock.json ]; then npm run build; \
  elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
  else echo "Lockfile not found." && exit 1; \
  fi

# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app

ENV NODE_ENV=production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED=1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT=3000

CMD ["node", "server.js"]

Build the Docker Image

Run the following command to build the Docker image (Docker should be installed on your development machine):

docker build -t my-next-app .

Run the Docker Container

Test your image locally with:

docker run -d -p 3000:3000 --name my-next-app my-next-app

You should be able to access the application at http://<your-vps-ip>:3000.

5. Configuring Caddy Server for Reverse Proxy and HTTPS

Caddy Server simplifies setting up HTTPS by automatically obtaining and renewing SSL certificates. Let’s configure it to serve the Next.js app.

Install Caddy

  • Install Caddy using the package manager:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/deb.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install -y caddy

Configure Caddy

Create a Caddyfile in /etc/caddy/:

mydomain.com {
    reverse_proxy localhost:3000
}

Replace mydomain.com with your actual domain name.

Test the Configuration

Reload Caddy to apply the configuration:

sudo systemctl reload caddy

Caddy will automatically fetch an SSL certificate for your domain.

6. Deploying the Application to the VPS

Now that we have the Docker image and Caddy configured, let’s deploy the application.

Transfer Files to the VPS

Use scp or another file transfer method to move your Next.js project to the VPS:

scp -r my-next-app <username>@<vps-ip>:/home/<username>/my-next-app

Build and Run the Docker Image on the VPS

  • SSH into your VPS: bash ssh <username>@<vps-ip>

  • Navigate to the project directory: bash cd my-next-app

  • Build the Docker image: bash docker build -t my-next-app .

  • Run the Docker container: bash docker run -d --name my-next-app -p 3000:3000 my-next-app

Verify Deployment

Visit your domain (https://mydomain.com) to verify the app is live.

Conclusion

In this guide, we’ve covered two options for deploying Next.js applications:

  1. Using the TurboCloud deployment toolkit with a single command. This option also includes CI/CD, multiple environments, a Web Application Firewall (WAF), a Rate Limiter, and VPN functionality across servers and local machines.
  2. Deploying manually with Docker, Docker Compose, and Caddy. To prepare your server for production, we recommend adding a Web Application Firewall (WAF) and a Rate Limiter.