Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize Docker for production builds #169

Merged
merged 8 commits into from
Jul 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/.storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ const config = {
builder: {
// Cache build output between runs, to speed up subsequent startup times
fsCache: true,
// Applies in development mode. Storybook will start up faster, at the cost
// of slightly slower browsing time when you navigate to another story.
lazyCompilation: true,
// lazyCompilation breaks Storybook when running from within Docker
// Google Translate this page for context: https://zenn.dev/yutaosawa/scraps/7764e5f17173d1
lazyCompilation: false,
},
},
},
Expand Down
2 changes: 1 addition & 1 deletion app/.storybook/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import i18nConfig from "../next-i18next.config";

// Apply global styling to our stories
import "../styles/styles.scss";
import "../src/styles/styles.scss";

// Import i18next config.
import i18n from "./i18next.js";
Expand Down
110 changes: 81 additions & 29 deletions app/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,31 +1,83 @@
# Use the Active LTS version of Node.
# See https://nodejs.org/en/about/releases/
FROM node:16-alpine
# Keep container packages up-to-date.
# -U runs both apk update and apk upgrade.
RUN apk -U upgrade


# setup user stuff
ARG RUN_UID
ARG RUN_USER

# The following logic creates the RUN_USER home directory and the directory where
# we will be storing the application in the image. This runs when the user is not root
RUN : "${RUN_USER:?RUN_USER and RUN_UID need to be set and non-empty.}" && \
[ "${RUN_USER}" = "root" ] || \
(addgroup -S "${RUN_USER}" && adduser -S -G "${RUN_USER}" -h "/home/${RUN_USER}" -u ${RUN_UID} "${RUN_USER}" \
&& mkdir /app \
&& chown -R "${RUN_USER}:${RUN_USER}" "/home/${RUN_USER}" /app)

USER ${RUN_USER}
# This file is largely based on the template-application-flask Dockerfile and
# Next.js Docker example: https://github.com/vercel/next.js/blob/canary/examples/with-docker-compose
# =============================================================================
FROM node:18-alpine AS base
WORKDIR /app

# Copy all the application files (ignoring files in .dockerignore) to the working directory.
COPY --chown="${RUN_USER}:${RUN_USER}" . /app
# Install application dependencies.
RUN npm ci
# Build the application.
RUN npm run build
# Run the application.
CMD ["npm", "run", "start"]
# Install dependencies
COPY package.json package-lock.json ./
COPY public ./public
COPY scripts ./scripts
RUN npm ci --no-audit

# =============================================================================
# Development stage
# =============================================================================
FROM base AS dev
WORKDIR /app

COPY tsconfig.json .
COPY *.config.js .
COPY *.d.ts .
COPY src ./src
COPY stories ./stories
COPY .storybook ./.storybook

ENV NEXT_TELEMETRY_DISABLED 1

CMD ["npm", "run", "dev"]

# =============================================================================
# Release stage
# =============================================================================

# Build the Next.js app
# =====================================
FROM base AS builder
WORKDIR /app

COPY tsconfig.json .
COPY *.config.js .
COPY *.d.ts .
COPY src ./src

# 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}

ENV NEXT_TELEMETRY_DISABLED 1

# Skip lint because it should have happened in the CI already
RUN npm run build -- --no-lint

# Run the Next.js server
# =====================================
FROM base AS release
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

# 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}
ENV NEXT_TELEMETRY_DISABLED 1
ENV PORT 3000

EXPOSE 3000

CMD ["node", "server.js"]
17 changes: 17 additions & 0 deletions app/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,27 @@ endif
export RUN_USER
export RUN_UID

##################################################
# Release
##################################################
release-build:
docker buildx build \
--platform=linux/amd64 \
--build-arg RUN_USER=$(RUN_USER) \
--build-arg RUN_UID=$(RUN_UID) \
$(OPTS) \
.

##################################################
# Local development
##################################################
dev: # Run the Next.js local dev server in Docker
docker compose up --detach nextjs
docker compose logs --follow nextjs

storybook: # Run the Storybook local dev server in Docker
docker compose up --detach storybook
docker compose logs --follow storybook

stop:
docker-compose down
53 changes: 39 additions & 14 deletions app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
├── .storybook # Storybook configuration
├── public # Static assets
│ └── locales # Internationalized content
├── src # JS source code
├── src # Source code
│ ├── components # Reusable UI components
│ └── pages # Page routes and data fetching
│   ├── api # API routes (optional)
│   └── _app.tsx # Global entry point
│ ├── pages # Page routes and data fetching
│   │ ├── api # API routes (optional)
│   │ └── _app.tsx # Global entry point
│ └── styles # Sass & design system settings
├── stories # Storybook pages
├── styles # Sass & design system settings
└── tests
```

Expand All @@ -31,6 +31,10 @@ Files in the `pages/api` are treated as [API routes](https://nextjs.org/docs/api

### Getting started

The application can be ran natively or in a Docker container.

#### Native

From the `app/` directory:

1. Install dependencies
Expand All @@ -47,33 +51,54 @@ From the `app/` directory:
```
1. Navigate to [localhost:3000](http://localhost:3000) to view the application

Alternatively, you can run the application in a Docker container:
##### Other scripts

- `npm run build` - Builds the production Next.js bundle
- `npm start` - Runs the Next.js server, after building the production bundle

#### Docker

Alternatively, you can run the application in a Docker container.

1. From the root directory run `docker compose up -d --build`
From the `app/` directory:

Other scripts:
1. Run the local development server
```bash
make dev
```
1. Navigate to [localhost:3000](http://localhost:3000) to view the application

- `npm run build` - Builds the production bundle
- `npm start` - Runs the app in production mode
##### Other scripts

- `make release-build` - Creates the Docker image for deployment to the cloud

## 🖼️ Storybook

Storybook is a [frontend workshop](https://bradfrost.com/blog/post/a-frontend-workshop-environment/) for developing and documenting pages and components in isolation. It allows you to render the same React components and files in the `src/` directory in a browser, without the need for a server or database. This allows you to develop and manually test components without having to run the entire Next.js application.

See the [Storybook Next.js documentation](https://github.com/storybookjs/storybook/tree/next/code/frameworks/nextjs) for more information about using Storybook with Next.js

Similar to the Next.js application, Storybook can be ran natively or in a Docker container.

#### Native

From the `app/` directory:

1. `npm run storybook`
2. Navigate to [localhost:6006](http://localhost:6006) to view

Alternatively, you can run Storybook in a Docker container:
##### Other scripts

1. From the root directory run `docker compose exec nextjs npm run storybook`
- `npm run storybook-build` - Exports a static site to `storybook-static/`

Other scripts:
#### Docker

- `npm run storybook-build` - Exports a static site to `storybook-static/`
Alternatively, you can run Storybook in a Docker container.

From the `app/` directory:

1. `make storybook`
2. Navigate to [localhost:6006](http://localhost:6006) to view

## 🐛 Testing

Expand Down
3 changes: 3 additions & 0 deletions app/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ const nextConfig = {
basePath,
i18n,
reactStrictMode: true,
// Output only the necessary files for a deployment, excluding irrelevant node_modules
// https://nextjs.org/docs/app/api-reference/next-config-js/output
output: "standalone",
sassOptions: appSassOptions,
transpilePackages: [
// Continue to support older browsers (ES5)
Expand Down
4 changes: 2 additions & 2 deletions app/src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { appWithTranslation } from "next-i18next";
import type { AppProps } from "next/app";
import Head from "next/head";

import "../../styles/styles.scss";

import Layout from "../components/Layout";

import "../styles/styles.scss";

function MyApp({ Component, pageProps }: AppProps) {
return (
<>
Expand Down
File renamed without changes.
File renamed without changes.
52 changes: 28 additions & 24 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
# Specify current major version of docker compose.
# See https://docs.docker.com/compose/compose-file/compose-versioning/
version: '3'

# Local development compose file
services:
# Define a service for the Next.js application.
nextjs:
build: ./app
# To support local development, runs the container in development mode.
# This allows Next.js to do things like hot reload.
command: ["npm", "run", "dev"]
container_name: next-dev
build:
context: ./app
target: dev
env_file:
# Add your non-secret environment variables to this file:
- ./app/.env.development
# If you have secrets, add them to this file and uncomment this line:
# - ./app/.env.local
volumes:
- ./app/src:/app/src
- ./app/public:/app/public
restart: always
ports:
# Expose a port to access the application.
- 3000:3000
# Expose a port for storybook.
- 6006:6006
volumes:
# Mount the app directory for faster local development.
- ./app:/srv
# Use a named volume for the node_modules so that the container uses
# the guest machine's node_modules dir instead of the host machine's node_modules directory, which might be divergent.
# This also means that if you run `npm install <package>` on the host machine in development
# (which will update `package-lock.lock`), you'll also need to run `docker compose exec nextjs npm ci` to update
# `node_modules` in the guest machine.
- nextjs_nodemodules:/srv/node_modules

volumes:
nextjs_nodemodules:

storybook:
container_name: storybook
build:
context: ./app
target: dev
command: npm run storybook
volumes:
- ./app/src:/app/src
- ./app/public:/app/public
- ./app/.storybook:/app/.storybook
- ./app/stories:/app/stories
restart: always
ports:
- 6006:6006