From d49f978a17673804311d8299a3af01792b2775b5 Mon Sep 17 00:00:00 2001 From: Lee Robinson Date: Sun, 28 Mar 2021 15:32:09 -0500 Subject: [PATCH] Update `with-docker` example and deployment docs. (#23486) ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [x] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. ## Documentation / Examples - [x] Make sure the linting passes --- docs/deployment.md | 10 +- examples/with-docker/.dockerignore | 7 +- examples/with-docker/Dockerfile | 33 +++++- examples/with-docker/Dockerfile.multistage | 28 ----- examples/with-docker/README.md | 59 ++++++---- examples/with-docker/package.json | 11 +- examples/with-docker/pages/_app.js | 7 ++ examples/with-docker/pages/api/hello.js | 5 + examples/with-docker/pages/index.js | 65 ++++++++++- examples/with-docker/public/favicon.ico | Bin 0 -> 15086 bytes examples/with-docker/public/vercel.svg | 7 +- examples/with-docker/styles/Home.module.css | 122 ++++++++++++++++++++ examples/with-docker/styles/globals.css | 16 +++ 13 files changed, 296 insertions(+), 74 deletions(-) delete mode 100644 examples/with-docker/Dockerfile.multistage create mode 100644 examples/with-docker/pages/_app.js create mode 100644 examples/with-docker/pages/api/hello.js create mode 100644 examples/with-docker/public/favicon.ico create mode 100644 examples/with-docker/styles/Home.module.css create mode 100644 examples/with-docker/styles/globals.css diff --git a/docs/deployment.md b/docs/deployment.md index ad9fc0151c..0d27ccf4aa 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -80,6 +80,13 @@ Make sure your `package.json` has the `"build"` and `"start"` scripts: ### Docker Image +
+ Examples + +
+ Next.js can be deployed to any hosting provider that supports [Docker](https://www.docker.com/) containers. You can use this approach when deploying to container orchestrators such as [Kubernetes](https://kubernetes.io/) or [HashiCorp Nomad](https://www.nomadproject.io/), or when running inside a single node in any cloud provider. Here is a multi-stage `Dockerfile` using `node:alpine` that you can use: @@ -111,6 +118,7 @@ ENV NODE_ENV production COPY --from=builder /app/public ./public COPY --from=builder /app/.next ./.next COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/package.json ./package.json RUN addgroup -g 1001 -S nodejs RUN adduser -S nextjs -u 1001 @@ -124,7 +132,7 @@ EXPOSE 3000 # Uncomment the following line in case you want to disable telemetry. # RUN npx next telemetry disable -CMD ["node_modules/.bin/next", "start"] +CMD ["yarn", "start"] ``` Make sure to place this Dockerfile in the root folder of your project. diff --git a/examples/with-docker/.dockerignore b/examples/with-docker/.dockerignore index fabc1be322..d862f96f1b 100644 --- a/examples/with-docker/.dockerignore +++ b/examples/with-docker/.dockerignore @@ -1,3 +1,6 @@ -.next/ -node_modules/ Dockerfile +.dockerignore +node_modules +npm-debug.log +README.md +.next \ No newline at end of file diff --git a/examples/with-docker/Dockerfile b/examples/with-docker/Dockerfile index 2d17c906a6..b08d4619a8 100644 --- a/examples/with-docker/Dockerfile +++ b/examples/with-docker/Dockerfile @@ -1,13 +1,36 @@ -FROM mhart/alpine-node - +# Install dependencies only when needed +FROM node:alpine 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 - -COPY . . - +COPY package.json yarn.lock ./ RUN yarn install --frozen-lockfile +# Rebuild the source code only when needed +FROM node:alpine AS builder +WORKDIR /app +COPY . . +COPY --from=deps /app/node_modules ./node_modules RUN yarn build +# Production image, copy all the files and run next +FROM node:alpine AS runner +WORKDIR /app + +ENV NODE_ENV production + +# You only need to copy next.config.js if you are NOT using the default configuration +# COPY --from=builder /app/next.config.js ./ +COPY --from=builder /app/public ./public +COPY --from=builder /app/.next ./.next +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/package.json ./package.json + +RUN addgroup -g 1001 -S nodejs +RUN adduser -S nextjs -u 1001 +RUN chown -R nextjs:nodejs /app/.next +USER nextjs + EXPOSE 3000 CMD ["yarn", "start"] diff --git a/examples/with-docker/Dockerfile.multistage b/examples/with-docker/Dockerfile.multistage deleted file mode 100644 index a69f05c2c6..0000000000 --- a/examples/with-docker/Dockerfile.multistage +++ /dev/null @@ -1,28 +0,0 @@ -# Stage 1: Building the code -FROM mhart/alpine-node AS builder - -WORKDIR /app - -COPY package.json yarn.lock ./ - -RUN yarn install --frozen-lockfile - -COPY . . - -RUN yarn build -RUN yarn install --production --frozen-lockfile - - -# Stage 2: And then copy over node_modules, etc from that stage to the smaller base image -FROM mhart/alpine-node:slim as production - -WORKDIR /app - -# COPY package.json next.config.js .env* ./ -COPY --from=builder /app/public ./public -COPY --from=builder /app/.next ./.next -COPY --from=builder /app/node_modules ./node_modules - -EXPOSE 3000 - -CMD ["node_modules/.bin/next", "start"] \ No newline at end of file diff --git a/examples/with-docker/README.md b/examples/with-docker/README.md index 055dd2881b..de4ba79fcd 100644 --- a/examples/with-docker/README.md +++ b/examples/with-docker/README.md @@ -1,42 +1,53 @@ # With Docker -This example shows how to set custom environment variables for your **docker application** at runtime. - -The `dockerfile` is the simplest way to run Next.js app in docker, and the size of output image is `173MB`. However, for an even smaller build, you can do multi-stage builds with `dockerfile.multistage`. The size of output image is `85MB`. - -You can check the [Example Dockerfile for your own Node.js project](https://github.com/mhart/alpine-node/tree/43ca9e4bc97af3b1f124d27a2cee002d5f7d1b32#example-dockerfile-for-your-own-nodejs-project) section in [mhart/alpine-node](https://github.com/mhart/alpine-node) for more details. +This examples shows how to use Docker with Next.js based on the [deployment documentation](https://nextjs.org/docs/deployment#docker-image). Additionally, it contains instructions for deploying to Google Cloud Run. However, you can use any container-based deployment host. ## 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) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: ```bash -npx create-next-app --example with-docker with-docker-app +npx create-next-app --example with-docker nextjs-docker # or -yarn create next-app --example with-docker with-docker-app +yarn create next-app --example with-docker nextjs-docker ``` -Build it with docker: +## Using Docker + +1. [Install Docker](https://docs.docker.com/get-docker/) on your machine. +1. Build your container: `docker build . -t nextjs-docker`. +1. Run your container: `docker run -p 3000:3000 nextjs-docker`. + +You can view your images created with `docker images`. + +## Deploying to Google Cloud Run + +The `start` script in `package.json` has been modified to accept a `PORT` environment variable (for compatability with Google Cloud Run). + +1. Install the [Google Cloud SDK](https://cloud.google.com/sdk/docs/install) so you can use `gcloud` on the command line. +1. Run `gcloud auth login` to log in to your account. +1. [Create a new project](https://cloud.google.com/run/docs/quickstarts/build-and-deploy) in Google Cloud Run (e.g. `nextjs-docker`). Ensure billing is turned on. +1. Build your container image using Cloud Build: `gcloud builds submit --tag gcr.io/PROJECT-ID/helloworld --project PROJECT-ID`. This will also enable Cloud Build for your project. +1. Deploy to Cloud Run: `gcloud run deploy --image gcr.io/PROJECT-ID/helloworld --project PROJECT-ID --platform managed`. Choose a region of your choice. + + - You will be prompted for the service name: press Enter to accept the default name, `helloworld`. + - You will be prompted for [region](https://cloud.google.com/run/docs/quickstarts/build-and-deploy#follow-cloud-run): select the region of your choice, for example `us-central1`. + - You will be prompted to **allow unauthenticated invocations**: respond `y`. + +## Running Locally + +First, run the development server: ```bash -# build -docker build -t next-app . -# or, use multi-stage builds to build a smaller docker image -docker build --target production -t next-app -f ./Dockerfile.multistage . +npm run dev +# or +yarn dev ``` -Alternatively you can add these commands as scripts to your package.json and simply run +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. -`yarn build-docker` -or -`yarn build-docker-multistage` +You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. -Run the docker image: +[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`. -```bash -docker run --rm -it \ - -p 3000:3000 \ - next-app -``` - -or use `yarn build-docker-multistage` +The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. diff --git a/examples/with-docker/package.json b/examples/with-docker/package.json index 388a8f4068..c4fc532c84 100644 --- a/examples/with-docker/package.json +++ b/examples/with-docker/package.json @@ -2,17 +2,14 @@ "name": "with-docker", "version": "1.0.0", "scripts": { - "dev": "next", + "dev": "next dev", "build": "next build", - "start": "next start", - "build-docker": "docker build -t next-app .", - "build-docker-multistage": "docker build --target production -t next-app -f ./Dockerfile.multistage .", - "run-docker": "docker run --rm -it -p 3000:3000 next-app" + "start": "next start -p $PORT" }, "dependencies": { "next": "latest", - "react": "^16.7.0", - "react-dom": "^16.7.0" + "react": "^17.0.2", + "react-dom": "^17.0.2" }, "license": "MIT" } diff --git a/examples/with-docker/pages/_app.js b/examples/with-docker/pages/_app.js new file mode 100644 index 0000000000..1e1cec9242 --- /dev/null +++ b/examples/with-docker/pages/_app.js @@ -0,0 +1,7 @@ +import '../styles/globals.css' + +function MyApp({ Component, pageProps }) { + return +} + +export default MyApp diff --git a/examples/with-docker/pages/api/hello.js b/examples/with-docker/pages/api/hello.js new file mode 100644 index 0000000000..441a0a1006 --- /dev/null +++ b/examples/with-docker/pages/api/hello.js @@ -0,0 +1,5 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction + +export default function hello(req, res) { + res.status(200).json({ name: 'John Doe' }) +} diff --git a/examples/with-docker/pages/index.js b/examples/with-docker/pages/index.js index 557f90a544..5787b11aa7 100644 --- a/examples/with-docker/pages/index.js +++ b/examples/with-docker/pages/index.js @@ -1,8 +1,65 @@ +import Head from 'next/head' +import styles from '../styles/Home.module.css' + export default function Home() { return ( - <> -

Hello World!

- Vercel - +
+ + Create Next App + + + +
+

+ Welcome to Next.js! +

+ +

+ Get started by editing{' '} + pages/index.js +

+ +
+ +

Documentation →

+

Find in-depth information about Next.js features and API.

+
+ + +

Learn →

+

Learn about Next.js in an interactive course with quizzes!

+
+ + +

Examples →

+

Discover and deploy boilerplate example Next.js projects.

+
+ + +

Deploy →

+

+ Instantly deploy your Next.js site to a public URL with Vercel. +

+
+
+
+ + +
) } diff --git a/examples/with-docker/public/favicon.ico b/examples/with-docker/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..4965832f2c9b0605eaa189b7c7fb11124d24e48a GIT binary patch literal 15086 zcmeHOOH5Q(7(R0cc?bh2AT>N@1PWL!LLfZKyG5c!MTHoP7_p!sBz0k$?pjS;^lmgJ zU6^i~bWuZYHL)9$wuvEKm~qo~(5=Lvx5&Hv;?X#m}i|`yaGY4gX+&b>tew;gcnRQA1kp zBbm04SRuuE{Hn+&1wk%&g;?wja_Is#1gKoFlI7f`Gt}X*-nsMO30b_J@)EFNhzd1QM zdH&qFb9PVqQOx@clvc#KAu}^GrN`q5oP(8>m4UOcp`k&xwzkTio*p?kI4BPtIwX%B zJN69cGsm=x90<;Wmh-bs>43F}ro$}Of@8)4KHndLiR$nW?*{Rl72JPUqRr3ta6e#A z%DTEbi9N}+xPtd1juj8;(CJt3r9NOgb>KTuK|z7!JB_KsFW3(pBN4oh&M&}Nb$Ee2 z$-arA6a)CdsPj`M#1DS>fqj#KF%0q?w50GN4YbmMZIoF{e1yTR=4ablqXHBB2!`wM z1M1ke9+<);|AI;f=2^F1;G6Wfpql?1d5D4rMr?#f(=hkoH)U`6Gb)#xDLjoKjp)1;Js@2Iy5yk zMXUqj+gyk1i0yLjWS|3sM2-1ECc;MAz<4t0P53%7se$$+5Ex`L5TQO_MMXXi04UDIU+3*7Ez&X|mj9cFYBXqM{M;mw_ zpw>azP*qjMyNSD4hh)XZt$gqf8f?eRSFX8VQ4Y+H3jAtvyTrXr`qHAD6`m;aYmH2zOhJC~_*AuT} zvUxC38|JYN94i(05R)dVKgUQF$}#cxV7xZ4FULqFCNX*Forhgp*yr6;DsIk=ub0Hv zpk2L{9Q&|uI^b<6@i(Y+iSxeO_n**4nRLc`P!3ld5jL=nZRw6;DEJ*1z6Pvg+eW|$lnnjO zjd|8>6l{i~UxI244CGn2kK@cJ|#ecwgSyt&HKA2)z zrOO{op^o*- - - + + + \ No newline at end of file diff --git a/examples/with-docker/styles/Home.module.css b/examples/with-docker/styles/Home.module.css new file mode 100644 index 0000000000..42e7e60094 --- /dev/null +++ b/examples/with-docker/styles/Home.module.css @@ -0,0 +1,122 @@ +.container { + min-height: 100vh; + padding: 0 0.5rem; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.main { + padding: 5rem 0; + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.footer { + width: 100%; + height: 100px; + border-top: 1px solid #eaeaea; + display: flex; + justify-content: center; + align-items: center; +} + +.footer img { + margin-left: 0.5rem; +} + +.footer a { + display: flex; + justify-content: center; + align-items: center; +} + +.title a { + color: #0070f3; + text-decoration: none; +} + +.title a:hover, +.title a:focus, +.title a:active { + text-decoration: underline; +} + +.title { + margin: 0; + line-height: 1.15; + font-size: 4rem; +} + +.title, +.description { + text-align: center; +} + +.description { + line-height: 1.5; + font-size: 1.5rem; +} + +.code { + background: #fafafa; + border-radius: 5px; + padding: 0.75rem; + font-size: 1.1rem; + font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, + Bitstream Vera Sans Mono, Courier New, monospace; +} + +.grid { + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + max-width: 800px; + margin-top: 3rem; +} + +.card { + margin: 1rem; + flex-basis: 45%; + padding: 1.5rem; + text-align: left; + color: inherit; + text-decoration: none; + border: 1px solid #eaeaea; + border-radius: 10px; + transition: color 0.15s ease, border-color 0.15s ease; +} + +.card:hover, +.card:focus, +.card:active { + color: #0070f3; + border-color: #0070f3; +} + +.card h3 { + margin: 0 0 1rem 0; + font-size: 1.5rem; +} + +.card p { + margin: 0; + font-size: 1.25rem; + line-height: 1.5; +} + +.logo { + height: 1em; +} + +@media (max-width: 600px) { + .grid { + width: 100%; + flex-direction: column; + } +} diff --git a/examples/with-docker/styles/globals.css b/examples/with-docker/styles/globals.css new file mode 100644 index 0000000000..e5e2dcc23b --- /dev/null +++ b/examples/with-docker/styles/globals.css @@ -0,0 +1,16 @@ +html, +body { + padding: 0; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, + Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; +} + +a { + color: inherit; + text-decoration: none; +} + +* { + box-sizing: border-box; +}