Introduction

Earlier this year, I bought Wes Bos' Advanced React course. It's a series of tutorials on using

It's an amazing course on full-stack javascript development and I highly recommend you check it out.

I've been working my way slowly and steadily through it and wanted to start my own experiments with prisma and graphql. But, one of the things on my mind was figuring out how to make something similar to the architecture he demonstrates but using docker as well.

This is the start of a series detailing what I learned while working on a new web app within that context. If you want to follow my progress on the app, you can

Pre-requisites

This series is going to assume some prior knowledge

  • What Docker is and why to use it
  • Javascript using ES6 syntax and patterns
  • React patterns

With that, let's get started!

Docker and Prisma

The first thing I wanted to do with my experiment was figure out how to create the backend of the application. It was an area where I felt like I had less experience and as usual, my music background kicked in and I figured

Do the hard thing first

Basically what I needed were three things

  1. A database
  2. Prisma for database operations
  3. A server

I knew that these three things needed to be hooked together within a docker context using docker-compose

The Good News

The good news is that prisma has a docker image and documentation on how to get started. When you install prisma in your project and use the prisma init command, the CLI lets you choose to use a new or existing database and have it "Set up a new Prisma server for local development (based on docker-compose)". Next you can choose the kind of database and whether or not to generate a client.

When you're done with the CLI prisma init creates three files based on your choices:

  • prisma.yml Prisma service definition
  • datamodel.prisma GraphQL SDL-based datamodel (foundation for database)
  • docker-compose.yml Docker configuration file

Great!

The Bad(ish) News

The bad news is that this doesn't actually create anything like a server. So I'm going to explain how I made a graphql-yoga server image. Fortunately, because of Wes' course, I had a kind of idea of what needed to be included and how I might like to use it in practice.

Creating an Image

One thing that I like to do with Docker images is create small images that I can extend and use for other things. For instance, here's my base image for node applications.

A Base Image

# aberkow/node-8
FROM node:8.10.0-alpine

LABEL "maintainer": "adam@adamjberkowitz.com" \
      "name": "Adam Berkowitz" \
      "project": "node base"

WORKDIR /project

COPY ./node/entrypoint.sh /entrypoint.sh

RUN apk update && apk add bash \
  && chmod +x /entrypoint.sh

EXPOSE 3000

ENTRYPOINT ["/entrypoint.sh"]

It's pretty straightforward and does the following things

  • create a /project working directory where the action happens
  • copy a small entrypoint.sh script into the image
  • run commands to
    • update the package manager
    • add bash
    • make the entrypoint script executable
  • make port 3000 available outside the running container
  • run the entrypoint script when the container starts

Here, I feel like it's worth explaining something.

Why use an entrypoint script?

Well I'll tell you! When docker runs, it performs its task and then exits as soon as that task is done. Using an entrypoint script allows for more flexibility and can allow for a few other fun things as I'll demonstrate in a moment.

Server Image

From here, this image can be extended to do all kinds of great stuff! Like this....

FROM aberkow/node-8

COPY prisma-graphql/src /project
COPY prisma-graphql/wait-for-it.sh /project/wait-for-it.sh
COPY prisma-graphql/entrypoint.sh /entrypoint.sh

RUN apk update && apk upgrade && apk add git \
  && npm set progress=false \
  && npm install -g prisma \
  && npm install \
  && chmod +x /entrypoint.sh \
  && chmod +x /project/wait-for-it.sh

EXPOSE 4000

This image

  • starts with the base image (and everything that includes)
  • copies assets into the image
    • wait-for-it.sh which is a "Pure bash script to test and wait on the availability of a TCP host and port"
    • a new entrypoint script
    • a package.json file
  • assets are installed
  • files are made executable
  • port 4000 is opened

Why doesn't this image have an entrypoint command? Because it comes along with the base image.

Wait for it...

The entrypoint script and wait-for-it are important. When docker runs the containers described by docker-compose, they don't wait for each other to. You can set the order in which they start. But you can't guarantee that a server (for instance) will be completely started after a database. That's what wait-for-it.sh helps with.

The entrypoint script that's copied into the image looks like this

#!/bin/bash

# wait for the prisma service to start. 
# then run prisma deploy (more on that later)
./project/wait-for-it.sh prisma:4466 -- prisma deploy

# go into the project...
cd /project

# run an npm command to use nodemon to start/watch the server
npm run dev

By setting the script this way, I can be sure that the database and prisma will always have the most up-to-date configuration. It will also ensure that the server will run.

For production, I can also easily override these commands by providing a different script and mounting it to the container via docker-compose

Using the new image

The prisma init command creates a docker-compose.yml file with two services like this

version: '3'
services:
  prisma:
    image: prismagraphql/prisma:1.23
    restart: always
    ports:
      - "4466:4466"
    environment:
      PRISMA_CONFIG: |
        port: 4466
        # uncomment the next line and provide the env var PRISMA_MANAGEMENT_API_SECRET=my-secret to activate cluster security
        # managementApiSecret: my-secret
        databases:
          default:
            connector: mysql
            host: mysql
            user: root
            password: prisma
            rawAccess: true
            port: 3306
            migrations: true
  mysql:
    image: mysql:5.7
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: prisma
    volumes:
      - mysql:/var/lib/mysql
volumes:
  mysql:

Next I built the server image with docker build -t prisma-server .. Then, I added another service to this file like this

version: '3'
services:
  server:
    image: prisma-server:latest
    ports:
      - "3000:3000"
      - "4000:4000"
    volumes:
      # optionally override the entrypoint
      # - ./entrypoint.sh:/entrypoint.sh
      # files for prisma
      - ./.graphqlconfig.yml:/project/.graphqlconfig.yml
      - ./prisma.yml:/project/prisma.yml
      - ./datamodel.prisma:/project/datamodel.prisma
      # The server and its dependencies
      - ./index.js:/project/index.js
      - ./src:/project/src
    environment:
      # needs to be an http address or else prisma freaks out
      PRISMA_ENDPOINT: http://prisma:4466
# other services etc...

Notice that here and in the entrypoint, the prisma service is referenced as prisma:4466 instead of with an IP address or url. This is because of the way docker's internal network handles communication between running containers.

Conclusion

By using a combination of prisma's default docker-compose file and my own custom server, I've got a flexible way to create an application based on graphql-yoga. In future posts, I'll describe the process how I learned to build a graphql backend for my application.