Earlier this year, I bought Wes Bos' Advanced React course. It's a series of tutorials on using
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
This series is going to assume some prior knowledge
- What Docker is and why to use it
- 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
- A database
- Prisma for database operations
- 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.ymlPrisma service definition
datamodel.prismaGraphQL SDL-based datamodel (foundation for database)
docker-compose.ymlDocker configuration file
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": "firstname.lastname@example.org" \ "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
/projectworking directory where the action happens
- copy a small
entrypoint.shscript 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.
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
- starts with the base image (and everything that includes)
copies assets into the image
wait-for-it.shwhich 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
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.
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.