graphql-framework-experiment icon indicating copy to clipboard operation
graphql-framework-experiment copied to clipboard

Docker guide

Open jasonkuhrt opened this issue 4 years ago • 28 comments

What

  • Create a guide for working with docker

  • users should take advantage of docker run -t to give their dev mode a tty

    #556 / https://github.com/graphql-nexus/nexus/issues/556#issuecomment-612606699

  • Show how to make work with sigterm:

     docker run --rm -e DEBUG=true -t t1 dev 
    832 ● nexus:dev start  --  version: '0.20.0-next.41'
    402 ● server listening  --  url: 'http://localhost:4000'
    C
    
  • show suggested Dockerfile (until we maintain official Nexus images), e.g. for hello-world example:

    ROM node:12
    
    ORKDIR /project
    
    OPY package.json yarn.lock ./
    UN yarn -s install
    
    OPY api tsconfig.json ./
    UN yarn -s build
    
    NTRYPOINT ["yarn", "-s"]
    
    MD ["start"]
    
  • Show how to get working with Alpine

jasonkuhrt avatar Apr 12 '20 12:04 jasonkuhrt

Any plans to support alpine version of the docker sometime in future? I know previously prisma had some issues with glibc. I managed to work it out using the below (still to optimise for layer caching), but an official support from alpine base image would be great.

FROM jeanblanchard/alpine-glibc:3.11
WORKDIR /app
COPY . ./
RUN apk add --no-cache --update nodejs npm \
  && SKIP_GENERATE="true" npm install \
  && npm run build \
  && npm prune --production \
  && apk del npm \
  && /bin/rm -rf /root/.cache /root/.config /root/.npm

ENV NODE_ENV production
EXPOSE 4000
CMD ["node", "node_modules/.build"]

paniavula avatar Apr 12 '20 16:04 paniavula

Yeah supporting Alpine would be great I think. Adding it to the above list.

jasonkuhrt avatar Apr 13 '20 09:04 jasonkuhrt

My 2¢ deployment, multistage, alpine DF:

FROM node:13 as base
RUN yarn global add node-gyp typescript@3 lerna
ENV SERVER_PKG=packages/server

##########################################################

FROM base as monorepo-base
WORKDIR /app

COPY yarn.lock package.json lerna.json ./
RUN yarn install --non-interactive --pure-lockfile

##########################################################

FROM monorepo-base as nexus-builder
WORKDIR /app/$SERVER_PKG

COPY $SERVER_PKG/package.json ./
RUN yarn install --non-interactive --pure-lockfile

COPY $SERVER_PKG/.env.example .
COPY $SERVER_PKG/tsconfig.json .
COPY $SERVER_PKG/prisma prisma
COPY $SERVER_PKG/api api
RUN yarn build

##########################################################

FROM node:13-alpine as server
ENV NODE_ENV=production
ENV SERVER_BUILD=/app/packages/server
WORKDIR $SERVER_BUILD

COPY --from=nexus-builder $SERVER_BUILD/package.json /app/yarn.lock ./
RUN yarn install --non-interactive --pure-lockfile --prod && yarn cache clean
COPY --from=nexus-builder ["/app/node_modules/@prisma/client", "node_modules/@prisma/client"]
COPY --from=nexus-builder $SERVER_BUILD/.env.example .
# Adjust for tsc outDir
COPY --from=nexus-builder $SERVER_BUILD/dist dist

CMD ["run", "start"]
ENTRYPOINT yarn

kazazes avatar May 08 '20 17:05 kazazes

@kazazes do you see any value in a dedicated docker image for Nexus? Meaning, one that you can use in the FROM line.

jasonkuhrt avatar May 12 '20 12:05 jasonkuhrt

@jasonkuhrt with your suggested Dockerfile, I get the error

{"path":["nexus","tsconfig"],"context":{},"event":"Your tsconfig.json is invalid\n\n\u001b[91merror\u001b[0m\u001b[90m TS18003: \u001b[0mNo inputs were found in config file '/project/tsconfig.json'. Specified 'include' paths were '[\"src\"]' and 'exclude' paths were '[\"node_modules/.build\"]'.\n","level":60}

(I am using src instead of api, FYI)

iherger avatar May 15 '20 13:05 iherger

@iherger can you share a repro?

jasonkuhrt avatar May 16 '20 02:05 jasonkuhrt

@jasonkuhrt How much would it differ from what's posted above? Besides fixing nexus version?

@iherger make sure your package paths are correctly defined in the ENV statements.

kazazes avatar May 19 '20 19:05 kazazes

@kazazes I don't think very much, but not sure until we look at this issue more closely.

jasonkuhrt avatar May 20 '20 12:05 jasonkuhrt

I think making a docker image for nexus would be a great way to get around the windows issues, as my company only uses Windows, so having a pre-made image would make the installation process much faster for me.

But for now I would be happy with just getting a guide on how to set up a docker project with Nexus.

fullStackDataSolutions avatar May 21 '20 17:05 fullStackDataSolutions

@kazazes Can you link the project that this docker file is pulling from?

fullStackDataSolutions avatar May 21 '20 17:05 fullStackDataSolutions

It's private. Is yours public? Happy to have a look.

kazazes avatar May 21 '20 18:05 kazazes

Unfortunately mine is private as well, but I'm just trying to work through the logic of yours. If you have the time maybe just make a small server and show how you would implement it?

Just for reference I'm trying to upgrade several services I created almost a year ago to the Nexus framework, some services are Prisma and some are API interfaces. But my dev environment has to be Windows, hence the need to dockerize it. However this fits in with future strategies to dockerize all our services and deploy the to clusters anyway.

So seeing how you are doing it with Prisma would be very helpful to me.

fullStackDataSolutions avatar May 21 '20 20:05 fullStackDataSolutions

Sure. Give me an hour.

kazazes avatar May 22 '20 15:05 kazazes

@blazestudios23 here you go.

kazazes avatar May 22 '20 15:05 kazazes

@kazazes Thanks a lot I'm looking it over, and I'm sure it will help me!!

fullStackDataSolutions avatar May 22 '20 18:05 fullStackDataSolutions

When I use docker I can get "nexus dev" to work and start a dev server, but running: RUN npm run build CMD ["npm", "start"]

Results in the following error: "Error: Cannot find module '/usr/app/node_modules/.build'"

I'm just using this example: https://github.com/graphql-nexus/examples/tree/master/hello-world

With the following docker file:

# This stage installs our modules
FROM node:alpine

WORKDIR /usr/app

COPY ./package.json ./
RUN npm install

COPY ./ ./

RUN npm run build

# CMD ["npm","run", "dev"] # works 
CMD ["npm", "start"] # doesn't work

EXPOSE 4000

fullStackDataSolutions avatar May 27 '20 00:05 fullStackDataSolutions

Could you provide a repro? Did it work in the example repo above?

kazazes avatar May 27 '20 12:05 kazazes

Here's the exact test repo I'm using: https://github.com/blazestudios23/docker-nexus-test

npm run test works but npm start does not.

fullStackDataSolutions avatar May 27 '20 17:05 fullStackDataSolutions

Typescript was building to .nexus/build. You can specify the output location in your tsconfig and adjust your start script to match.

diff --git a/package.json b/package.json
index efa4067..90615bd 100644
--- a/package.json
+++ b/package.json
@@ -14,7 +14,7 @@
   },
   "scripts": {
     "clean": "rm -rf dist",
-    "start": "node node_modules/.build",
+    "start": "node dist",
     "build": "nexus build",
     "generate": "nexus generate",
     "dev": "nexus dev",
@@ -27,7 +27,7 @@
     "preset": "ts-jest",
     "testEnvironment": "node"
   },
-  "main": "index.js",
+  "main": "dist/index.js",
   "directories": {
     "test": "tests"
   },
diff --git a/tsconfig.json b/tsconfig.json
index ac768be..e95141e 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -2,7 +2,8 @@
   "compilerOptions": {
     "strict": true,
     "esModuleInterop": true,
-    "rootDir": "api"
+    "rootDir": ".",
+    "outDir": "dist"
   },
-  "include": ["api"]
+  "include": ["."]
 }

Easiest way to debug things like this is to run your own cmd in the docker image instead of the CMD line.

$ docker run -p 4000:4000 --rm nt **ls node_modules/.build**                                                                                                                                                          
ls: node_modules/.build: No such file or directory
$ docker run -p 4000:4000 --rm nt ls dist 
api
index.js
node_modules

kazazes avatar May 27 '20 18:05 kazazes

@kazazes Thanks on the debugging tips those are helpful. I'm not a total docker Ninja yet.

I actually tried that .nexus/build it didn't work either. And I tried to find it in the node_modules folder, but didn't see it at all.

fullStackDataSolutions avatar May 27 '20 19:05 fullStackDataSolutions

@kazazes So it's creating a .nexus file and then not putting anything in it during the build process.

/usr/src/app/node_modules # cd .nexus
/usr/src/app/node_modules/.nexus # ls -a
.                  ..                 cache.tsbuildinfo

There is no .build file as it shows in the example.

fullStackDataSolutions avatar May 27 '20 23:05 fullStackDataSolutions

node_modules/.nexus should contain the ts build cache.

$PROJECT_ROOT/.nexus/build/api would contain the build output in a project that doesn't specify an outDir, not the node_module directory.

kazazes avatar May 28 '20 12:05 kazazes

OK their example project was completely off, it was in: .nexus/build/api

First off the fact that I'm running it as a Docker Image made it harder to find, second putting the build in the .nexus file made it harder to find, as files . are hidden when you do ls, you have to use the -a flag. But was thrown a red hearing by their example which put the files in the wrong directory.

There is literally nothing worse than a Hello World Example that doesn't even work.

fullStackDataSolutions avatar May 28 '20 16:05 fullStackDataSolutions

@blazestudios23 this version of nexus is iterating very quickly, but I feel ya. I updated the docs in #939.

kazazes avatar May 29 '20 00:05 kazazes

So I'm now trying to get the testing library working in the simple example repo that Nexus made. But I get an error from this code:

import { createTestContext, TestContext } from "nexus/testing"

let ctx = {} as TestContext

beforeAll(async () => {
  const testContext = await createTestContext()
  Object.assign(ctx, testContext)
  await ctx.app.start()
})

afterAll(async () => {
  await ctx.app.stop()
})

it("works", async () => {
  expect(
    await ctx.app.query(`
      query {
        users {
          id
        }
      }
    `)
  ).toMatchInlineSnapshot(`
    Object {
      "users": Array [
        Object {
          "id": "1",
        },
      ],
    }
  `)
})

The error says that ctx.app,query doesn't exist. What's the work around for this?

fullStackDataSolutions avatar Jul 06 '20 21:07 fullStackDataSolutions

@blazestudios23 Looks like we missed mentioning this in one of our releases. The API for app client changed, see https://nexusjs.org/api/nexus-testing#i-testcontext.

ctx.client.send(...)

jasonkuhrt avatar Jul 06 '20 21:07 jasonkuhrt

I've been trying various formulas here to get my instance up and running - both the simple one by OP @jasonkuhrt and the optimized multistage by noble sir @kazazes

Having issues here:

#25 0.895 $ nexus build
#25 2.354 {"event":"get used plugins","level":3,"path":["nexus","build"]}
#25 2.818 {"event":"failed to get used plugins","level":6,"path":["nexus"],"context":{"error":{"code":"MODULE_NOT_FOUND","requireStack":["/app/packages/server/api/FirebaseAdmin/FirebaseAdmin.ts","/app/packages/server/api/graphql/firebase/firebase_API.ts","/app/node_modules/nexus/dist/runtime/start/dev-runner.js","/app/node_modules/nexus/dist/runtime/start/index.js","/app/node_modules/nexus/dist/lib/reflection/reflect.js","/app/node_modules/nexus/dist/lib/plugin/worktime.js","/app/node_modules/nexus/dist/cli/commands/create/app.js","/app/node_modules/nexus/dist/cli/commands/create/index.js","/app/node_modules/nexus/dist/cli/commands/index.js","/app/node_modules/nexus/dist/cli/main.js"]}}}

EDIT: Got everything running fine now... the MODULE_NOT_FOUND was confusing me because that's all I was able to see outside of the container so I thought that the errors were from dependencies for the nexus plugins I was using... but indeed the problem was on my end.

If someone else stumbles upon similar issues to debug I just added an intermediate entrypoint as follows: ENTRYPOINT ["/bin/bash"] ... and then ran the yarn -s build inside the container, and was able to see much more verbose complaints about the missing modules (pretty general advise for Docker)

Here's the file I ultimately used (derived from https://github.com/kazazes/nexus-prisma-docker but minor modification using the newer .nexus/build path)

##########################################################
# Setup a base image to build other packages from.       #
# Only work as 'node' user with uid and gid 1000.        #
##########################################################
FROM node:14 as monorepo-base
WORKDIR /app
ENV SERVER_PKG=packages/server
#ENV FRONTEND_PKG=packages/frontend

RUN mkdir -p $SERVER_PKG $FRONTEND_PKG \
    && chown -R 1000:1000 /app \
    && chmod -R 700 /app

USER node
WORKDIR /app

# Make the yarn cache to a writeable location, install node-gyp
RUN yarn config set cache-folder $HOME/.yarn-cache
RUN yarn global add --silent node-gyp

COPY --chown=node yarn.lock package.json lerna.json ./

##########################################################
# Build the API server to /app/packages/server/dist      #
##########################################################
FROM monorepo-base as nexus-builder
WORKDIR /app/$SERVER_PKG

RUN touch .env
COPY --chown=node $SERVER_PKG/package.json ./
RUN yarn install --silent --non-interactive --pure-lockfile
#COPY --chown=node $SERVER_PKG/.env.example .
COPY --chown=node $SERVER_PKG/tsconfig.json .
COPY --chown=node $SERVER_PKG/prisma prisma
COPY --chown=node $SERVER_PKG/api api

#RUN export $(cat .env.example) && yarn build
RUN yarn -s build


##########################################################
# Build a deployment image with the -slim variant, only  #
# including the necessary files from nexus-builder        #
##########################################################
FROM node:14-slim as server

ENV SERVER_BUILD=/app/packages/server
ENV NODE_ENV=production
RUN mkdir -p $SERVER_BUILD && chown -R 1000:1000 /app
WORKDIR $SERVER_BUILD

USER node
RUN yarn config set cache-folder $HOME/.yarn-cache

COPY --chown=node --from=nexus-builder $SERVER_BUILD/package.json /app/yarn.lock ./
RUN yarn install --silent --non-interactive --pure-lockfile --prod && yarn cache clean
COPY --chown=node --from=nexus-builder ["/app/node_modules/@prisma/client", "node_modules/@prisma/client"]
#COPY --chown=node --from=nexus-builder $SERVER_BUILD/.env.example .
COPY --chown=node --from=nexus-builder $SERVER_BUILD/.nexus/build dist

CMD ["node", "dist/index.js"]
HEALTHCHECK --interval=60s --timeout=10s --start-period=30s \
  CMD curl -f http://localhost:4000/graphql || exit 1


nargetdev avatar Jul 23 '20 03:07 nargetdev

this docker recipe doesn't seem to work with the recent versions of nexus and plugins:

{
    "nexus": "^0.26.1",
    "nexus-plugin-jwt-auth": "^1.3.1",
    "nexus-plugin-prisma": "^0.17.0",
    "nexus-plugin-shield": "^0.2.0"
}

I used the example found here: https://github.com/graphql-nexus/examples/tree/master/plugins-prisma-and-jwt-auth-and-shield

I got it to successfully build and run on my local machine with nexus build and node .nexus/build/api

and modified the Dockerfile to fit that new setup to build the image:

##########################################################
# Setup a base image to build other packages from.       #
# Only work as 'node' user with uid and gid 1000.        #
##########################################################
FROM node:14 as monorepo-base
WORKDIR /app

#ENV FRONTEND_PKG=packages/frontend

RUN chown -R 1000:1000 /app \
    && chmod -R 700 /app

USER node
WORKDIR /app

# Make the yarn cache to a writeable location, install node-gyp
RUN yarn config set cache-folder $HOME/.yarn-cache
RUN yarn global add --silent node-gyp

COPY --chown=node yarn.lock package.json ./

##########################################################
# Build the API server to /app/packages/server/dist      #
##########################################################
FROM monorepo-base as nexus-builder
WORKDIR /app

RUN touch .env
COPY --chown=node package.json ./
RUN yarn install --silent --non-interactive --pure-lockfile
#COPY --chown=node .env.example .
COPY --chown=node tsconfig.json .
COPY --chown=node prisma prisma
COPY --chown=node api api

#RUN export $(cat .env.example) && yarn build
RUN yarn -s build


##########################################################
# Build a deployment image with the -slim variant, only  #
# including the necessary files from nexus-builder        #
##########################################################
FROM node:14-slim as server


ENV NODE_ENV=production
RUN mkdir /app && chown -R 1000:1000 /app
WORKDIR /app

USER node
RUN yarn config set cache-folder $HOME/.yarn-cache

COPY --chown=node --from=nexus-builder /app/package.json /app/yarn.lock ./
RUN yarn install --silent --non-interactive --pure-lockfile --prod && yarn cache clean
COPY --chown=node --from=nexus-builder ["/app/node_modules/@prisma/client", "node_modules/@prisma/client"]
#COPY --chown=node --from=nexus-builder .env.example .
COPY --chown=node --from=nexus-builder /app/.nexus/build dist

CMD ["node", "dist/api"]
HEALTHCHECK --interval=60s --timeout=10s --start-period=30s \
    CMD curl -f http://localhost:4000/graphql || exit 1

with the following docker-compose.yml

version: "3.7"
services:
  server:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: nexus-prisma-docker
    restart: unless-stopped
    init: true
    ports:
      - 4000:4000

I get an error relating to permission being denied on port 80 of all things:

✕ app uncaughtException
  | error  Error: listen EACCES: permission denied 0.0.0.0:80
  |            at Server.setupListenHandle [as _listen2] (net.js:1299:21)
  |            at listenInCluster (net.js:1364:12)
  |            at Server.listen (net.js:1450:7)
  |            at /app/dist/node_modules/nexus/dist/lib/utils/index.js:359:16
  |            at new Promise (<anonymous>)
  |            at Object.httpListen (/app/dist/node_modules/nexus/dist/lib/utils/index.js:358:12)
  |            at Object.start (/app/dist/node_modules/nexus/dist/runtime/server/server.js:79:31)
  |            at Object.start (/app/dist/node_modules/nexus/dist/runtime/app.js:72:43)
  |            at Object.<anonymous> (/app/dist/api/index.js:28:17)
  |            at Module._compile (internal/modules/cjs/loader.js:1256:30) {
  |          code: 'EACCES',
  |          errno: -13,
  |          syscall: 'listen',
  |          address: '0.0.0.0',
  |          port: 80
  |        }

do you guys have any idea what is causing this issue?

MJGTwo avatar Aug 11 '20 21:08 MJGTwo