From 3cd927ec306de7517d3eb22cca5fd91ebd4ac0b2 Mon Sep 17 00:00:00 2001 From: You Nguyen Date: Sun, 6 Feb 2022 01:13:01 +0700 Subject: [PATCH] [New Example] with docker - multiple deployment environments (#34015) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Documentation / Examples - [x] Make sure the linting passes by running `yarn lint` --- ## Context Having 3 environments: - Development: for doing testing - Staging: for doing UAT testing - Production: for users In each environment, the Next.js application makes API calls to the corresponding API gateway: - Development: https://api-development.com - Staging: https://api-staging.com - Production: https://api-production.com Using `NEXT_PUBLIC_API_URL` for the `baseUrl` of [axios](https://axios-http.com/docs/intro). Since the `NEXT_PUBLIC_API_URL` is replaced during _build time_, we have to manage to provide the corresponding `.env.production` files for Docker at _build time_ for each environment. ## Solution Since we are using CI services for dockerization, we could setup the CI to inject the correct `.env.production` file into the cloned source code, (this is actually what we did). Doing that would require us to touch the CI settings. Another way is using multiple Dockerfile (the former only need to use one Dockerfile), and the trick is copying the corresponding `env*.sample` and rename it to `.env.production` then putting it into the Docker context. Doing this way, everything is managed in the source code. ``` > Dockerfile # Development environment COPY .env.development.sample .env.production # Staging environment COPY .env.staging.sample .env.production # Production environment COPY .env.production.sample .env.production ``` Testing these images locally is also simple, by issuing the corresponding Makefile commands we can simulate exactly how the image will be built in the CI environment. ## How to use For development environment: ``` make build-development make start-development ``` For staging environment: ``` make build-staging make start-staging ``` For production environment: ``` make build-production make start-production ``` ## Conclusion This example shows one way to solve the three-environment model in software development when building a Next.js application. There might be another better way and I would love to know about them as well. I'm making this example because I can't find any example about this kind of problem. Co-authored-by: Tú Nguyễn <93700515+tunguyen-ct@users.noreply.github.com> --- examples/with-docker-multi-env/.dockerignore | 7 + examples/with-docker-multi-env/.env | 1 + .../.env.development.sample | 1 + .../.env.production.sample | 1 + .../with-docker-multi-env/.env.staging.sample | 1 + examples/with-docker-multi-env/.gitignore | 38 ++++++ examples/with-docker-multi-env/Makefile | 35 +++++ examples/with-docker-multi-env/README.md | 62 +++++++++ .../docker/development/Dockerfile | 45 +++++++ .../docker/development/docker-compose.yml | 10 ++ .../docker/production/Dockerfile | 45 +++++++ .../docker/production/docker-compose.yml | 10 ++ .../docker/staging/Dockerfile | 45 +++++++ .../docker/staging/docker-compose.yml | 10 ++ examples/with-docker-multi-env/next.config.js | 5 + examples/with-docker-multi-env/package.json | 13 ++ examples/with-docker-multi-env/pages/_app.js | 7 + .../with-docker-multi-env/pages/api/hello.js | 5 + examples/with-docker-multi-env/pages/index.js | 66 ++++++++++ .../with-docker-multi-env/public/favicon.ico | Bin 0 -> 15086 bytes .../with-docker-multi-env/public/vercel.svg | 4 + .../styles/Home.module.css | 122 ++++++++++++++++++ .../with-docker-multi-env/styles/globals.css | 16 +++ 23 files changed, 549 insertions(+) create mode 100644 examples/with-docker-multi-env/.dockerignore create mode 100644 examples/with-docker-multi-env/.env create mode 100644 examples/with-docker-multi-env/.env.development.sample create mode 100644 examples/with-docker-multi-env/.env.production.sample create mode 100644 examples/with-docker-multi-env/.env.staging.sample create mode 100644 examples/with-docker-multi-env/.gitignore create mode 100644 examples/with-docker-multi-env/Makefile create mode 100644 examples/with-docker-multi-env/README.md create mode 100644 examples/with-docker-multi-env/docker/development/Dockerfile create mode 100644 examples/with-docker-multi-env/docker/development/docker-compose.yml create mode 100644 examples/with-docker-multi-env/docker/production/Dockerfile create mode 100644 examples/with-docker-multi-env/docker/production/docker-compose.yml create mode 100644 examples/with-docker-multi-env/docker/staging/Dockerfile create mode 100644 examples/with-docker-multi-env/docker/staging/docker-compose.yml create mode 100644 examples/with-docker-multi-env/next.config.js create mode 100644 examples/with-docker-multi-env/package.json create mode 100644 examples/with-docker-multi-env/pages/_app.js create mode 100644 examples/with-docker-multi-env/pages/api/hello.js create mode 100644 examples/with-docker-multi-env/pages/index.js create mode 100644 examples/with-docker-multi-env/public/favicon.ico create mode 100644 examples/with-docker-multi-env/public/vercel.svg create mode 100644 examples/with-docker-multi-env/styles/Home.module.css create mode 100644 examples/with-docker-multi-env/styles/globals.css diff --git a/examples/with-docker-multi-env/.dockerignore b/examples/with-docker-multi-env/.dockerignore new file mode 100644 index 0000000000000..4d72057ef2861 --- /dev/null +++ b/examples/with-docker-multi-env/.dockerignore @@ -0,0 +1,7 @@ +Dockerfile +.dockerignore +node_modules +npm-debug.log +README.md +.next +docker diff --git a/examples/with-docker-multi-env/.env b/examples/with-docker-multi-env/.env new file mode 100644 index 0000000000000..1affd3c0a776c --- /dev/null +++ b/examples/with-docker-multi-env/.env @@ -0,0 +1 @@ +NEXT_PUBLIC_API_URL=http://localhost diff --git a/examples/with-docker-multi-env/.env.development.sample b/examples/with-docker-multi-env/.env.development.sample new file mode 100644 index 0000000000000..357551fe6e464 --- /dev/null +++ b/examples/with-docker-multi-env/.env.development.sample @@ -0,0 +1 @@ +NEXT_PUBLIC_API_URL=https://api-development.com diff --git a/examples/with-docker-multi-env/.env.production.sample b/examples/with-docker-multi-env/.env.production.sample new file mode 100644 index 0000000000000..a566eb6948ed4 --- /dev/null +++ b/examples/with-docker-multi-env/.env.production.sample @@ -0,0 +1 @@ +NEXT_PUBLIC_API_URL=https://api-production.com diff --git a/examples/with-docker-multi-env/.env.staging.sample b/examples/with-docker-multi-env/.env.staging.sample new file mode 100644 index 0000000000000..b1c8d9f9b19d4 --- /dev/null +++ b/examples/with-docker-multi-env/.env.staging.sample @@ -0,0 +1 @@ +NEXT_PUBLIC_API_URL=https://api-staging.com diff --git a/examples/with-docker-multi-env/.gitignore b/examples/with-docker-multi-env/.gitignore new file mode 100644 index 0000000000000..10d8c913baeaa --- /dev/null +++ b/examples/with-docker-multi-env/.gitignore @@ -0,0 +1,38 @@ +# 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 + +# lock files +yarn.lock +package-lock.json diff --git a/examples/with-docker-multi-env/Makefile b/examples/with-docker-multi-env/Makefile new file mode 100644 index 0000000000000..759e426b4a7a1 --- /dev/null +++ b/examples/with-docker-multi-env/Makefile @@ -0,0 +1,35 @@ +.PHONY: build-development +build-development: ## Build the development docker image. + docker compose -f docker/development/docker-compose.yml build + +.PHONY: start-development +start-development: ## Start the development docker container. + docker compose -f docker/development/docker-compose.yml up -d + +.PHONY: stop-development +stop-development: ## Stop the development docker container. + docker compose -f docker/development/docker-compose.yml down + +.PHONY: build-staging +build-staging: ## Build the staging docker image. + docker compose -f docker/staging/docker-compose.yml build + +.PHONY: start-staging +start-staging: ## Start the staging docker container. + docker compose -f docker/staging/docker-compose.yml up -d + +.PHONY: stop-staging +stop-staging: ## Stop the staging docker container. + docker compose -f docker/staging/docker-compose.yml down + +.PHONY: build-production +build-production: ## Build the production docker image. + docker compose -f docker/production/docker-compose.yml build + +.PHONY: start-production +start-production: ## Start the production docker container. + docker compose -f docker/production/docker-compose.yml up -d + +.PHONY: stop-production +stop-production: ## Stop the production docker container. + docker compose -f docker/production/docker-compose.yml down diff --git a/examples/with-docker-multi-env/README.md b/examples/with-docker-multi-env/README.md new file mode 100644 index 0000000000000..2799adfd136ae --- /dev/null +++ b/examples/with-docker-multi-env/README.md @@ -0,0 +1,62 @@ +# With Docker - Multiple Deployment Environments + +This examples shows how to use Docker with Next.js and deploy to multiple environment with different env values. Based on the [deployment documentation](https://nextjs.org/docs/deployment#docker-image). + +## 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-multi-env nextjs-docker-multi-env +# or +yarn create next-app --example with-docker-multi-env nextjs-docker-multi-env +``` + +Enter the values in the `.env.development.sample`, `.env.staging.sample`, `.env.production.sample` files to be used for each environments. + +## Using Docker and Makefile + +### Development environment - for doing testing + +``` +make build-development +make start-development +``` + +Open http://localhost:3001 + +### Staging environment - for doing UAT testing + +``` +make build-staging +make start-staging +``` + +Open http://localhost:3002 + +### Production environment - for users + +``` +make build-production +make start-production +``` + +Open http://localhost:3003 + +## Running Locally + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. + +[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`. + +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-multi-env/docker/development/Dockerfile b/examples/with-docker-multi-env/docker/development/Dockerfile new file mode 100644 index 0000000000000..d5cce6d2d12c1 --- /dev/null +++ b/examples/with-docker-multi-env/docker/development/Dockerfile @@ -0,0 +1,45 @@ +# 1. Install dependencies only when needed +FROM node:16-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 package.json yarn.lock ./ +RUN yarn install --frozen-lockfile + +# 2. Rebuild the source code only when needed +FROM node:16-alpine AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . +# This will do the trick, use the corresponding env file for each environment. +COPY .env.development.sample .env.production +RUN yarn build + +# 3. Production image, copy all the files and run next +FROM node:16-alpine AS runner +WORKDIR /app + +ENV NODE_ENV=production + +RUN addgroup -g 1001 -S nodejs +RUN adduser -S nextjs -u 1001 + +# 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/package.json ./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 + + +USER nextjs + +EXPOSE 3000 + +ENV PORT 3000 + +CMD ["node", "server.js"] diff --git a/examples/with-docker-multi-env/docker/development/docker-compose.yml b/examples/with-docker-multi-env/docker/development/docker-compose.yml new file mode 100644 index 0000000000000..cdf32d5e953d3 --- /dev/null +++ b/examples/with-docker-multi-env/docker/development/docker-compose.yml @@ -0,0 +1,10 @@ +version: '3' + +services: + with-docker-multi-env-development: + build: + context: ../../ + dockerfile: docker/development/Dockerfile + image: with-docker-multi-env-development + ports: + - '3001:3000' diff --git a/examples/with-docker-multi-env/docker/production/Dockerfile b/examples/with-docker-multi-env/docker/production/Dockerfile new file mode 100644 index 0000000000000..d26bdb2fef663 --- /dev/null +++ b/examples/with-docker-multi-env/docker/production/Dockerfile @@ -0,0 +1,45 @@ +# 1. Install dependencies only when needed +FROM node:16-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 package.json yarn.lock ./ +RUN yarn install --frozen-lockfile + +# 2. Rebuild the source code only when needed +FROM node:16-alpine AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . +# This will do the trick, use the corresponding env file for each environment. +COPY .env.production.sample .env.production +RUN yarn build + +# 3. Production image, copy all the files and run next +FROM node:16-alpine AS runner +WORKDIR /app + +ENV NODE_ENV=production + +RUN addgroup -g 1001 -S nodejs +RUN adduser -S nextjs -u 1001 + +# 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/package.json ./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 + + +USER nextjs + +EXPOSE 3000 + +ENV PORT 3000 + +CMD ["node", "server.js"] diff --git a/examples/with-docker-multi-env/docker/production/docker-compose.yml b/examples/with-docker-multi-env/docker/production/docker-compose.yml new file mode 100644 index 0000000000000..b903f1ddd8353 --- /dev/null +++ b/examples/with-docker-multi-env/docker/production/docker-compose.yml @@ -0,0 +1,10 @@ +version: '3' + +services: + with-docker-multi-env-production: + build: + context: ../../ + dockerfile: docker/production/Dockerfile + image: with-docker-multi-env-production + ports: + - '3003:3000' diff --git a/examples/with-docker-multi-env/docker/staging/Dockerfile b/examples/with-docker-multi-env/docker/staging/Dockerfile new file mode 100644 index 0000000000000..beb44b1b39622 --- /dev/null +++ b/examples/with-docker-multi-env/docker/staging/Dockerfile @@ -0,0 +1,45 @@ +# 1. Install dependencies only when needed +FROM node:16-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 package.json yarn.lock ./ +RUN yarn install --frozen-lockfile + +# 2. Rebuild the source code only when needed +FROM node:16-alpine AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . +# This will do the trick, use the corresponding env file for each environment. +COPY .env.staging.sample .env.production +RUN yarn build + +# 3. Production image, copy all the files and run next +FROM node:16-alpine AS runner +WORKDIR /app + +ENV NODE_ENV=production + +RUN addgroup -g 1001 -S nodejs +RUN adduser -S nextjs -u 1001 + +# 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/package.json ./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 + + +USER nextjs + +EXPOSE 3000 + +ENV PORT 3000 + +CMD ["node", "server.js"] diff --git a/examples/with-docker-multi-env/docker/staging/docker-compose.yml b/examples/with-docker-multi-env/docker/staging/docker-compose.yml new file mode 100644 index 0000000000000..231ef1e61a718 --- /dev/null +++ b/examples/with-docker-multi-env/docker/staging/docker-compose.yml @@ -0,0 +1,10 @@ +version: '3' + +services: + with-docker-multi-env-staging: + build: + context: ../../ + dockerfile: docker/staging/Dockerfile + image: with-docker-multi-env-staging + ports: + - '3002:3000' diff --git a/examples/with-docker-multi-env/next.config.js b/examples/with-docker-multi-env/next.config.js new file mode 100644 index 0000000000000..0568ecc9e5401 --- /dev/null +++ b/examples/with-docker-multi-env/next.config.js @@ -0,0 +1,5 @@ +module.exports = { + experimental: { + outputStandalone: true, + }, +} diff --git a/examples/with-docker-multi-env/package.json b/examples/with-docker-multi-env/package.json new file mode 100644 index 0000000000000..f9170ae254fa3 --- /dev/null +++ b/examples/with-docker-multi-env/package.json @@ -0,0 +1,13 @@ +{ + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "next": "latest", + "react": "^17.0.2", + "react-dom": "^17.0.2" + } +} diff --git a/examples/with-docker-multi-env/pages/_app.js b/examples/with-docker-multi-env/pages/_app.js new file mode 100644 index 0000000000000..1e1cec92425c8 --- /dev/null +++ b/examples/with-docker-multi-env/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-multi-env/pages/api/hello.js b/examples/with-docker-multi-env/pages/api/hello.js new file mode 100644 index 0000000000000..441a0a1006e5a --- /dev/null +++ b/examples/with-docker-multi-env/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-multi-env/pages/index.js b/examples/with-docker-multi-env/pages/index.js new file mode 100644 index 0000000000000..51e835d5e8720 --- /dev/null +++ b/examples/with-docker-multi-env/pages/index.js @@ -0,0 +1,66 @@ +import Head from 'next/head' +import styles from '../styles/Home.module.css' + +export default function Home() { + return ( +
+ + Create Next App + + + +
+

+ Welcome to Next.js on Docker! +

+

with Multiple Deployment Environments

+

API_URL: {process.env.NEXT_PUBLIC_API_URL}

+

+ 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-multi-env/public/favicon.ico b/examples/with-docker-multi-env/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-multi-env/styles/Home.module.css b/examples/with-docker-multi-env/styles/Home.module.css new file mode 100644 index 0000000000000..42e7e6009497f --- /dev/null +++ b/examples/with-docker-multi-env/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-multi-env/styles/globals.css b/examples/with-docker-multi-env/styles/globals.css new file mode 100644 index 0000000000000..e5e2dcc23baf1 --- /dev/null +++ b/examples/with-docker-multi-env/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; +}