Deploy a GraphQL Server with Docker and Fly
Tags: docker, graphql, express, fly
Published: 2021-09-15 08:04:14

---
canonical_url: "https://ajcwebdev.com/deploy-gql-docker-container-with-fly/"
description: "Create a GraphQL server with Express, build a Docker image of the server, and deploy the container to Fly."
---

> ___All of this project's code can be found in the [First Look monorepo](https://github.com/ajcwebdev/a-first-look/tree/main/deployment/docker-express-graphql/) on my GitHub.___

## Introduction

[Express GraphQL](https://github.com/graphql/express-graphql/) is a library for building production ready GraphQL HTTP middleware. Despite the emphasis on Express in the repo name, you can create a GraphQL HTTP server with any HTTP web framework that supports connect styled middleware. This includes [Connect](https://github.com/senchalabs/connect) itself, [Express](https://expressjs.com) and [Restify](http://restify.com/).

[Docker](https://www.docker.com/) is a set of tools that use OS-level virtualization to deliver software in isolated packages called containers. Containers bundle their own software, libraries and configuration files. [Fly](https://fly.io/) is a platform for full stack applications and databases that need to run globally. You can run arbitrary Docker containers and host popular databases like Postgres.

## Create a GraphQL Express Server

This article will demonstrate how to create a Docker container with Express GraphQL.

### Create Project and Install Dependencies

```bash
mkdir ajcwebdev-express-graphql-docker
cd ajcwebdev-express-graphql-docker
yarn init -y
yarn add express express-graphql graphql
```

Create project files for the server, Docker image, and Docker Compose configuration.

```bash
echo > index.js
echo > Dockerfile
echo > docker-compose.yml
```

Before the Docker CLI sends the context to the Docker daemon, it looks for a file named `.dockerignore` in the root directory of the context and modifies the context to exclude files and directories that match patterns defined in the ignore file. This helps avoid sending large or sensitive files and directories to the daemon.

```bash
echo 'node_modules\nDockerfile\n.dockerignore\n.git\n.gitignore\nnpm-debug.log' > .dockerignore
```

Include a `.gitignore` file for `node_modules`.

```bash
echo 'node_modules\n.DS_Store' > .gitignore
```

### Create graphqlHTTP Server

Enter the following code into `index.js` to import the `graphqlHTTP` function from `express-graphql`.

```js
// index.js

const express = require('express')
const { graphqlHTTP } = require('express-graphql')
const { buildSchema } = require('graphql')

const schema = buildSchema(`
  type Query { hello: String }
`)

const rootValue = {
  hello: () => 'Hello from Express GraphQL!'
}

const app = express()

app.use('/graphql',
  graphqlHTTP({
    schema,
    rootValue,
    graphiql: { headerEditorEnabled: true },
  }),
)

const port = process.env.PORT || 8080

app.listen(port, () => {
  console.log(`Express GraphQL server running on http://localhost:${port}/graphql`)
})
```

`graphqlHTTP` accepts a wide range of options, some of the most common include:

- **`schema`** - A `GraphQLSchema` instance from `GraphQL.js`
- **`rootValue`** - A value to pass as the `rootValue` to the `execute()` function
- **`graphiql`** - If passed `true` or an options object it will present GraphiQL when the GraphQL endpoint is loaded in a browser
- **`headerEditorEnabled`** - Optional boolean which enables the header editor when `true`

### Run Local Server and Execute Test Query

`express-graphql` will accept requests with the parameters:

- **`query`** - A string GraphQL document to be executed
- **`variables`** - The runtime values to use for any GraphQL query variables as a JSON object
- **`operationName`** - Specifies which operation should be executed if the provided `query` contains multiple named operations

Start your server with the following command:

```bash
node index
```

Your terminal will log this message:

```
Express GraphQL server running on http://localhost:8080/graphql
```

Open [http://localhost:8080/graphql](http://localhost:8080/graphql) to see the GraphiQL explorer.

![01-graphiql-explorer-on-localhost-8080](https://cdn.hashnode.com/res/hashnode/image/upload/v1654503189104/bCnmLUYvU.png)

```graphql
query HELLO_QUERY { hello }
```

![02-express-graphql-hello-localhost-8080](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6tb9ltduj8k6lfiwwwb9.png)

```bash
curl 'http://localhost:8080/graphql' \
  --header 'content-type: application/json' \
  --data '{"query":"{ hello }"}'
```

## Create a Container Image

We need to build a Docker image of your app to run this app inside a Docker container.

### Dockerfile Commands

Docker can build images automatically by reading the instructions from a [`Dockerfile`](https://docs.docker.com/engine/reference/builder/). A `Dockerfile` is a text document that contains all the commands a user could call on the command line to assemble an image. Using `docker build` users can create an automated build that executes several command-line instructions in succession.

```dockerfile
FROM node:14-alpine
LABEL org.opencontainers.image.source https://github.com/ajcwebdev/ajcwebdev-express-graphql-docker
WORKDIR /usr/src/app
COPY package*.json ./
RUN yarn
COPY . ./
EXPOSE 8080
CMD [ "node", "index" ]
```

### Build the Image

The [`docker build`](https://docs.docker.com/engine/reference/commandline/build/) command builds an image from a `Dockerfile` and a "context". A build’s context is the set of files located in the specified `PATH` or `URL`. The `URL` parameter can refer to three kinds of resources:
* Git repositories
* Pre-packaged tarball contexts
* Plain text files

```bash
docker build . -t ajcwebdev/ajcwebdev-express-graphql-docker
```

The `-t` flag lets you tag your image so it's easier to find later using the `docker images` command.

### List Docker Images

Your image will now be listed by Docker. The [`docker images`](https://docs.docker.com/engine/reference/commandline/images/) command will list all top level images, their repository and tags, and their size.

```bash
docker images
```

```
REPOSITORY - ajcwebdev/ajcwebdev-express-graphql-docker
TAG - latest
IMAGE ID - d833d418e179
CREATED - About a minute ago
SIZE - 122MB
```

## Run the Docker Container and Execute a Test Query

Docker runs processes in isolated containers. A container is a process which runs on a host. The host may be local or remote. When an operator executes [`docker run`](https://docs.docker.com/engine/reference/run/), the container process that runs is isolated in that it has its own file system, its own networking, and its own isolated process tree separate from the host.

```bash
docker run -p 49160:8080 -d ajcwebdev/ajcwebdev-express-graphql-docker
```

`-d` runs the container in detached mode, leaving the container running in the background. The `-p` flag redirects a public port to a private port inside the container.

### List Containers

To test your app, get the port of your app that Docker mapped:

```bash
docker ps
```

```
CONTAINER ID - 4bdd108175ab
IMAGE - ajcwebdev/ajcwebdev-express-graphql-docker
COMMAND - "docker-entrypoint.s…"
CREATED - 16 seconds ago
STATUS - Up 14 seconds
PORTS - 0.0.0.0:49160->8080/tcp, :::49160->8080/tcp
NAMES - silly_greider
```

Print the output of your app with `docker logs`.

```bash
docker logs <container id>
```

```
Express GraphQL server running on http://localhost:8080/graphql
```

Docker mapped the `8080` port inside of the container to the port `49160` on your machine. Open [localhost:49160/graphql](http://localhost:49160/graphql) and send a hello query.

```graphql
query HELLO_QUERY { hello }
```

![03-localhost-49160-graphql](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bpim4bg6oi7z09r4w3hw.png)

```bash
curl 'http://localhost:49160/graphql' \
  --header 'content-type: application/json' \
  --data '{"query":"{ hello }"}'
```

### Create a Docker Compose File

[Compose](https://docs.docker.com/compose/) is a tool for defining and running multi-container Docker applications. After configuring your application’s services with a YAML file, you can create and start all your services with a single command. Define the services that make up your app in `docker-compose.yml` so they can be run together in an isolated environment.

```yaml
version: "3.9"
services:
  web:
    build: .
    ports:
      - "49160:8080"
```

Stop your currently running container before running the next command or the port will be in use.

```bash
docker stop <container id>
```

The `docker compose up` command aggregates the output of each container. It builds, (re)creates, starts, and attaches to containers for a service.

```bash
docker compose up
```

## Publish to GitHub Container Registry

We can publish this image to the GitHub Container Registry with GitHub Packages. This will require pushing our project to a GitHub repository.

### Create GitHub Repository

Initialize Git, create a blank repository, and push to newly created repo

```bash
git init
git add .
git commit -m "A container for my graph"
gh repo create ajcwebdev-express-graphql-docker --public \
  --push \
  --source=. \
  --description="An example GraphQL Express server containerized with Docker and deployed on Fly" \
  --remote=upstream
```

[GitHub Packages](https://docs.github.com/en/packages/learn-github-packages/introduction-to-github-packages) is a platform for hosting and managing packages that combines your source code and packages in one place including containers and other dependencies. You can integrate GitHub Packages with GitHub APIs, GitHub Actions, and webhooks to create an end-to-end DevOps workflow that includes your code, CI, and deployment solutions.

GitHub Packages offers different package registries for commonly used package managers, such as npm, RubyGems, Maven, Gradle, and Docker. GitHub's [Container registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry) is optimized for containers and supports Docker and OCI images.

### Login to the GitHub Container Registry

To login, create a [PAT (personal access token)](https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token) with the ability to read, write, and delete packages and include it instead of `xxxx`.

```bash
export CR_PAT=xxxx
```

Login with your own username in place of `ajcwebdev`.

```bash
echo $CR_PAT | docker login ghcr.io -u ajcwebdev --password-stdin
```

### Tag Image

```bash
docker tag ajcwebdev/ajcwebdev-express-graphql-docker ghcr.io/ajcwebdev/ajcwebdev-express-graphql-docker
```

### Push to Registry

```bash
docker push ghcr.io/ajcwebdev/ajcwebdev-express-graphql-docker:latest
```

### Pull Image

To test that our project has a docker image published to a public registry, pull it from your local development environment.

```bash
docker pull ghcr.io/ajcwebdev/ajcwebdev-express-graphql-docker
```

You can view this published container [on my GitHub](https://github.com/users/ajcwebdev/packages/container/package/ajcwebdev-express-graphql-docker).

## Deploy to Fly

You can download the `flyctl` CLI on [Mac, Linux, or Windows](https://fly.io/docs/getting-started/installing-flyctl/).

```bash
brew install superfly/tap/flyctl
```

### Install and Authenticate Fly CLI

If you are a new user you can create an account with `fly auth signup`.

```bash
fly auth signup
```

You will also be prompted for credit card payment information, required for charges outside the free plan on Fly. See [Pricing](https://fly.io/docs/about/pricing) for more details. If you already have an account you can login with `fly auth login`.

```bash
fly auth login
```

### Launch App on Fly

Run `fly launch` in the directory with your source code to configure your app for deployment. This will create and configure a fly app by inspecting your source code and prompting you to deploy.

```bash
fly launch --name ajcwebdev-express-graphql-docker
```

Select a region and skip adding a PostgreSQL database. When asked if you want to deploy, select **No**.

```
Your app is ready. Deploy with `flyctl deploy`
? Would you like to deploy now? No
```

The launch command created a `fly.toml` file.

```toml
app = "ajcwebdev-express-graphql-docker"

kill_signal = "SIGINT"
kill_timeout = 5
processes = []

[env]

[experimental]
  allowed_public_ports = []
  auto_rollback = true

[[services]]
  http_checks = []
  internal_port = 8080
  processes = ["app"]
  protocol = "tcp"
  script_checks = []

[services.concurrency]
  hard_limit = 25
  soft_limit = 20
  type = "connections"

[[services.ports]]
  handlers = ["http"]
  port = 80

[[services.ports]]
  handlers = ["tls", "http"]
  port = 443

[[services.tcp_checks]]
  grace_period = "1s"
  interval = "15s"
  restart_limit = 6
  timeout = "2s"
```

Add the following `PORT` number under `env`.

```toml
[env]
  PORT = 8080
```

### Deploy Fly Application

```bash
fly deploy
```

### Show the Application Status

Status includes application details, tasks, most recent deployment details and in which regions it is currently allocated.

```bash
fly status
```

```
App
  Name     = ajcwebdev-express-graphql-docker          
  Owner    = personal                                  
  Version  = 0                                         
  Status   = running                                   
  Hostname = ajcwebdev-express-graphql-docker.fly.dev  

Deployment Status
  ID          = fd7bf249-c37f-7b16-5643-9bfd104a2077         
  Version     = v0                                           
  Status      = successful                                   
  Description = Deployment completed successfully            
  Instances   = 1 desired, 1 placed, 1 healthy, 0 unhealthy  

Instances
ID       TASK VERSION REGION DESIRED STATUS  HEALTH CHECKS      RESTARTS CREATED   
9eb4eaf9 app  0       sjc    run     running 1 total, 1 passing 0        1m15s ago
```

Visit [ajcwebdev-express-graphql-docker.fly.dev/graphql](https://ajcwebdev-express-graphql-docker.fly.dev/graphql) to see the site and run a test query.

```graphql
query HELLO_QUERY { hello }
```

![04-ajcwebdev-express-graphql-docker-fly-dev-hello](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e7ab8m96y7j8frtxnwg8.png)

```bash
curl 'https://ajcwebdev-express-graphql-docker.fly.dev/graphql' \
  --header 'content-type: application/json' \
  --data '{"query":"{ hello }"}'
```