Skip to content

Commit

Permalink
Add with-docker-compose example (#32668)
Browse files Browse the repository at this point in the history
`with-docker-compose` contains everything needed to get Next.js up and running with Docker Compose.

This example aims to provide an easy-to-use, Next.js app development and production environment, **all without Node.js being installed** on the host machine. This ensures a consistent development environment across Windows, MacOS, and Linux teams.

I was inspired to create this, because the existing [with-docker](https://github.com/vercel/next.js/tree/canary/examples/with-docker) example only uses Docker to build the final production artifacts, not provide a development environment. Docker Compose easily syncs changes with containers for Hot Reloading, parallel builds, and networking, making it a powerful and consistent development tool.

Developers can **easily extend this example** by modifying the YAML files to include Nginx, Postgres, and other Docker images. 

This example takes advantage of Docker multistage builds combined with the Next 12.1 [Output Standalone](https://nextjs.org/docs/advanced-features/output-file-tracing#automatically-copying-traced-files-experimental) feature, to create up to **80% smaller apps** (Approximately 110 MB compared to 1 GB with create-next-app). I also included an example without multistage builds, for developers who don't want to get into the weeds.

I have been tweaking this Docker Compose setup over 3 years of real world use, but please let me know if anything can be improved.
  • Loading branch information
maxproske committed Jun 14, 2022
1 parent 3a22c4c commit 78809a3
Show file tree
Hide file tree
Showing 21 changed files with 611 additions and 0 deletions.
Empty file.
5 changes: 5 additions & 0 deletions examples/with-docker-compose/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# DO NOT ADD SECRETS TO THIS FILE. This is a good place for defaults.
# If you want to add secrets use `.env.production.local` instead, which is automatically detected by docker-compose.

ENV_VARIABLE=production_server_only_variable
NEXT_PUBLIC_ENV_VARIABLE=production_public_variable
34 changes: 34 additions & 0 deletions examples/with-docker-compose/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local

# vercel
.vercel
92 changes: 92 additions & 0 deletions examples/with-docker-compose/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# With Docker Compose

This example contains everything needed to get a Next.js development and production environment up and running with Docker Compose.

## Benfits of Docker Compose

- Develop locally without Node.js or TypeScript installed ✨
- Easy to run, consistent development environment across Mac, Windows, and Linux teams
- Run multiple Next.js apps, databases, and other microservices in a single deployment
- Multistage builds combined with [Output Standalone](https://nextjs.org/docs/advanced-features/output-file-tracing#automatically-copying-traced-files-experimental) outputs up to 85% smaller apps (Approximately 110 MB compared to 1 GB with create-next-app)
- BuildKit engine builds multiple Docker images in parallel
- Easy configuration with YAML files

## How to use

Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init), [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/), or [pnpm](https://pnpm.io) to bootstrap the example:

```bash
npx create-next-app --example with-docker-compose with-docker-compose-app
# or
yarn create next-app --example with-docker-compose with-docker-compose-app
# or
pnpm create next-app --example with-docker-compose with-docker-compose-app
```

## Prerequisites

Install [Docker Desktop](https://docs.docker.com/get-docker) for Mac, Windows, or Linux. Docker Desktop includes Docker Compose as part of the installation.

## Development

First, run the development server:

```bash
# Create a network, which allows containers to communicate
# with each other, by using their container name as a hostname
docker network create my_network

# Build dev using new BuildKit engine
COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f docker-compose.dev.yml build --parallel

# Up dev
docker-compose -f docker-compose.dev.yml up
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.

## Production

Multistage builds are highly recommended in production. Combined with the Next 12 [Output Standalone](https://nextjs.org/docs/advanced-features/output-file-tracing#automatically-copying-traced-files-experimental) feature, only `node_modules` files required for production are copied into the final Docker image.

First, run the production server (Final image approximately 110 MB).

```bash
# Create a network, which allows containers to communicate
# with each other, by using their container name as a hostname
docker network create my_network

# Build prod using new BuildKit engine
COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f docker-compose.prod.yml build --parallel

# Up prod in detached mode
docker-compose -f docker-compose.prod.yml up -d
```

Alternatively, run the production server without without multistage builds (Final image approximately 1 GB).

```bash
# Create a network, which allows containers to communicate
# with each other, by using their container name as a hostname
docker network create my_network

# Build prod without multistage using new BuildKit engine
COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f docker-compose.prod-without-multistage.yml build --parallel

# Up prod without multistage in detached mode
docker-compose -f docker-compose.prod-without-multistage.yml up -d
```

Open [http://localhost:3000](http://localhost:3000).

## Useful commands

```bash
# Stop all running containers
docker kill $(docker ps -q) && docker rm $(docker ps -a -q)

# Free space
docker system prune -af --volumes
```
27 changes: 27 additions & 0 deletions examples/with-docker-compose/docker-compose.dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
version: '3'

services:
next-app:
container_name: next-app
build:
context: ./next-app
dockerfile: dev.Dockerfile
environment:
ENV_VARIABLE: ${ENV_VARIABLE}
NEXT_PUBLIC_ENV_VARIABLE: ${NEXT_PUBLIC_ENV_VARIABLE}
volumes:
- ./next-app/src:/app/src
- ./next-app/public:/app/public
restart: always
ports:
- 3000:3000
networks:
- my_network

# Add more containers below (nginx, postgres, etc.)

# Define a network, which allows containers to communicate
# with each other, by using their container name as a hostname
networks:
my_network:
external: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
version: '3'

services:
next-app:
container_name: next-app
build:
context: ./next-app
dockerfile: prod-without-multistage.Dockerfile
args:
ENV_VARIABLE: ${ENV_VARIABLE}
NEXT_PUBLIC_ENV_VARIABLE: ${NEXT_PUBLIC_ENV_VARIABLE}
restart: always
ports:
- 3000:3000
networks:
- my_network

# Add more containers below (nginx, postgres, etc.)

# Define a network, which allows containers to communicate
# with each other, by using their container name as a hostname
networks:
my_network:
external: true
24 changes: 24 additions & 0 deletions examples/with-docker-compose/docker-compose.prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
version: '3'

services:
next-app:
container_name: next-app
build:
context: ./next-app
dockerfile: prod.Dockerfile
args:
ENV_VARIABLE: ${ENV_VARIABLE}
NEXT_PUBLIC_ENV_VARIABLE: ${NEXT_PUBLIC_ENV_VARIABLE}
restart: always
ports:
- 3000:3000
networks:
- my_network

# Add more containers below (nginx, postgres, etc.)

# Define a network, which allows containers to communicate
# with each other, by using their container name as a hostname
networks:
my_network:
external: true
34 changes: 34 additions & 0 deletions examples/with-docker-compose/next-app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local

# vercel
.vercel
15 changes: 15 additions & 0 deletions examples/with-docker-compose/next-app/dev.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM node:18-alpine

WORKDIR /app

# Copy lock files if file exists
COPY package.json yarn.lock* package-lock.json* .

RUN yarn install

COPY src ./src
COPY public ./public
COPY next.config.js .
COPY tsconfig.json .

CMD yarn dev
5 changes: 5 additions & 0 deletions examples/with-docker-compose/next-app/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
5 changes: 5 additions & 0 deletions examples/with-docker-compose/next-app/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
experimental: {
outputStandalone: true,
},
}
19 changes: 19 additions & 0 deletions examples/with-docker-compose/next-app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "latest",
"react": "18.1.0",
"react-dom": "18.1.0"
},
"devDependencies": {
"@types/node": "17.0.42",
"@types/react": "18.0.12",
"@types/react-dom": "18.0.5",
"typescript": "4.7.3"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
FROM node:18-alpine

WORKDIR /app

# Copy lock files if file exists
COPY package.json yarn.lock* package-lock.json* .

# Omit --production flag for TypeScript devDependencies
RUN yarn install

COPY src ./src
COPY public ./public
COPY next.config.js .
COPY tsconfig.json .

# Environment variables must be present at build time
# https://github.com/vercel/next.js/discussions/14030
ARG ENV_VARIABLE
ENV ENV_VARIABLE=${ENV_VARIABLE}
ARG NEXT_PUBLIC_ENV_VARIABLE
ENV NEXT_PUBLIC_ENV_VARIABLE=${NEXT_PUBLIC_ENV_VARIABLE}

# Uncomment the following line to disable telemetry at build time
# ENV NEXT_TELEMETRY_DISABLED 1

RUN yarn build

CMD yarn start
57 changes: 57 additions & 0 deletions examples/with-docker-compose/next-app/prod.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Step 1. Rebuild the source code only when needed
FROM node:18-alpine AS builder

WORKDIR /app

# Copy lock files if file exists
COPY package.json yarn.lock* package-lock.json* .

# Omit --production flag for TypeScript devDependencies
RUN yarn install

COPY src ./src
COPY public ./public
COPY next.config.js .
COPY tsconfig.json .

# Environment variables must be present at build time
# https://github.com/vercel/next.js/discussions/14030
ARG ENV_VARIABLE
ENV ENV_VARIABLE=${ENV_VARIABLE}
ARG NEXT_PUBLIC_ENV_VARIABLE
ENV NEXT_PUBLIC_ENV_VARIABLE=${NEXT_PUBLIC_ENV_VARIABLE}

# Uncomment the following line to disable telemetry at build time
# ENV NEXT_TELEMETRY_DISABLED 1

RUN yarn build

# Step 2. Production image, copy all the files and run next
FROM node:18-alpine AS runner

WORKDIR /app

# Don't run production as root
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
USER nextjs

COPY --from=builder /app/public ./public
COPY --from=builder /app/next.config.js .
COPY --from=builder /app/package.json .

# 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

# Environment variables must be redefined at run time
ARG ENV_VARIABLE
ENV ENV_VARIABLE=${ENV_VARIABLE}
ARG NEXT_PUBLIC_ENV_VARIABLE
ENV NEXT_PUBLIC_ENV_VARIABLE=${NEXT_PUBLIC_ENV_VARIABLE}

# Uncomment the following line to disable telemetry at run time
# ENV NEXT_TELEMETRY_DISABLED 1

CMD node server.js
Binary file not shown.
4 changes: 4 additions & 0 deletions examples/with-docker-compose/next-app/public/vercel.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions examples/with-docker-compose/next-app/src/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import '../styles/globals.css'
import type { AppProps } from 'next/app'

export default function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
Loading

0 comments on commit 78809a3

Please sign in to comment.