import { Callout } from '@theguild/components'

# Docker

Docker is a tool that allows you to package an application and its dependencies into a container
that can run on any system. This makes it easy to deploy applications in a consistent and
reproducible way, regardless of the underlying infrastructure.

To simplify running your GraphQL gateway, you can use the Docker image and the Docker Compose
template we provide. This setup allows you to easily configure and run the gateway without the need
to install Node.js and the required gateway npm packages.

## Prerequisites

Make sure you have Docker installed on your system.

You can follow [the official Docker Engine install manual](https://docs.docker.com/engine/install/)
in case you don't have Docker installed already.

## Configuration

### Arguments

Hive Gateway can be configured with [CLI arguments](/docs/api-reference/gateway-cli#arguments) even
when running the image!

For example, changing the supergraph to use the `my-schema.graphql` schema instead looks like this:

```sh
docker run \
  -p 4000:4000 \
  -v "$(pwd)/my-schema.graphql:/serve/my-schema.graphql" \
  ghcr.io/ardatan/hive-gateway supergraph my-schema.graphql
```

For a full list of CLI arguments, please refer to the
[CLI arguments](/docs/api-reference/gateway-cli#arguments).

### Config File

Instead of configuring Hive Gateway with CLI arguments, we support configuring with a config file.

You're recommended to use the `gateway.config.ts` file to configure Hive Gateway. Simply mount the
config file when running the image.

```ts filename="gateway.config.ts"
import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
  proxy: {
    endpoint: 'https://example.com/graphql'
  }
})
```

```sh
docker run \
  -p 4000:4000 \
  -v "$(pwd)/gateway.config.ts:/serve/gateway.config.ts" \
  ghcr.io/ardatan/hive-gateway proxy
```

For a full list of CLI arguments, please refer to the
[Config Reference](/docs/api-reference/gateway-config).

### Changing Port in Container

The default port where Hive Gateway listens is `4000`; however, maybe the container is running
inside a network (like when using
[Networking in Compose](https://docs.docker.com/compose/networking/)) and you wish to change the
port of Hive Gateway in the image.

You can use the `gateway.config.ts` to change the port, or simply pass in the `--port` argument when
running the image:

```sh
docker run \
  -p 8080:8080 \
  -v "$(pwd)/supergraph.graphql:/serve/supergraph.graphql" \
  ghcr.io/ardatan/hive-gateway supergraph --port=8080
```

## Running

Having a `supergraph.graphql` already composed with [GraphQL Mesh](https://graphql-mesh.com/),
running the Docker image is as easy as:

```sh
docker run \
  -p 4000:4000 \
  -v "$(pwd)/supergraph.graphql:/serve/gateway.config.ts" \
  ghcr.io/ardatan/hive-gateway supergraph
```

## Docker Compose

You may have an environment where you want to use [Docker Compose](https://docs.docker.com/compose/)
and would like to add Hive Gateway there.

Start by defining the `docker-compose.yml`

```yaml
services:
  hive-gateway:
    image: ghcr.io/ardatan/hive-gateway
    command: supergraph
    ports:
      - '4000:4000'
    # Add Hive Registry environment variables in case you use it
    # environment:
    #   HIVE_CDN_ENDPOINT: <secret>
    #   HIVE_CDN_KEY: <secret>
    #   HIVE_REGISTRY_TOKEN: <secret>
    volumes:
      - ./gateway.config.ts:/serve/gateway.config.ts
```

And then simply start the services with:

```sh
docker compose up
```

## Extend Docker Image

### Install Plugin

You may want to add additional functionality, or plugins to the base image - you just need to create
a new Dockerfile basing the image off `ghcr.io/ardatan/hive-gateway`.

If need only a handful of plugins (or some other dependencies), you can simply extend the image and
install the modules with `npm i`:

For example, adding
[Block Field Suggestions Plugin](/docs/gateway/other-features/security/block-field-suggestions) to
the container would look like this:

```dockerfile filename="Dockerfile"
FROM ghcr.io/ardatan/hive-gateway

RUN npm i @escape.tech/graphql-armor-block-field-suggestions
```

```sh
docker build -t hive-gateway-w-block-suggestions .
```

Configure to use the block field suggestions plugin:

```ts filename="gateway.config.ts"
import { blockFieldSuggestionsPlugin } from '@escape.tech/graphql-armor-block-field-suggestions'
import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
  plugins: pluginCtx => [
    blockFieldSuggestionsPlugin({
      // Toggle the plugin | Default: true
      enabled: true,
      // Mask applied to the error message | default: '[Suggestion hidden]'
      mask: '[Suggestion hidden]'
    })
  ]
})
```

And then simply start the new image with the config file mounted:

```sh
docker run \
  -p 4000:4000 \
  -v "$(pwd)/gateway.config.ts:/serve/gateway.config.ts" \
  hive-gateway-w-block-suggestions supergraph
```

### Develop Plugin

However, you may be developing a plugin and have a setup with some dependencies and source code,
copying over your project's files is the way to go.

In the following example, we're developing a `useTiming` plugin that will add a human readable
execution duration to the GraphQL result `extensions` property.

```json filename="package.json"
{
  "name": "my-timing",
  "dependencies": {
    "moment": "^2"
  },
  "devDependencies": {
    "@graphql-hive/gateway": "latest",
    "@graphql-hive/gateway": "latest"
  }
}
```

```ts filename="my-timing.ts"
import moment from 'moment'
import type { GatewayPlugin } from '@graphql-hive/gateway'

export function useTiming(): GatewayPlugin {
  return {
    onExecute() {
      const start = Date.now()
      return {
        onExecuteDone({ result, setResult }) {
          const duration = moment.duration(Date.now() - start)
          if (isAsyncIterable(result)) {
            setResult(
              mapAsyncIterator(result, result => ({
                ...result,
                extensions: {
                  ...result?.extensions,
                  duration: duration.humanize()
                }
              }))
            )
            return
          }
          setResult({
            ...result,
            extensions: {
              ...result?.extensions,
              duration: duration.humanize()
            }
          })
        }
      }
    }
  }
}
```

```ts filename="gateway.config.ts"
import { defineConfig } from '@graphql-hive/gateway'
import { useTiming } from './my-timing'

export const gatewayConfig = defineConfig({
  plugins: () => [useTiming()]
})
```

Your Dockerfile should then look something like this:

```dockerfile filename="Dockerfile"
FROM ghcr.io/ardatan/hive-gateway

# we dont install dev deps because:
#   1. we need them for type checking only
#   2. Hive Gateway is already available in the docker image
COPY package*.json .
RUN npm i --omit=dev

COPY my-time.ts .
COPY gateway.config.ts .
```

Then build your image:

```sh
docker build -t hive-gateway-w-my-timing .
```

And finally start it (the config file is in the image and doesn't need to be mounted):

```sh
docker run -p 4000:4000 hive-gateway-w-my-timing supergraph
```

<Callout>
  For faster development, you can mount the source code as volumes so that you don't have to rebuild
  the image on each run.

```sh
docker run -p 4000:4000 \
  -v "$(pwd)/gateway.config.ts":/serve/gateway.config.ts \
  -v "$(pwd)/my-timing.ts":/serve/my-timing.ts \
  hive-gateway-w-my-timing supergraph
```

</Callout>

### Additional Resolvers

Instead maybe you need to define additional resolvers that depend on other dependencies. Similarily
to the [Develop Plugin](#develop-plugin) approach, you can just copy the project code over and build
another image.

Say you have the following files:

```json filename="package.json"
{
  "name": "my-time",
  "dependencies": {
    "moment": "^2"
  },
  "devDependencies": {
    "@graphql-hive/gateway": "latest"
  }
}
```

```js filename="my-time.ts"
import moment from 'moment'

export const additionalResolvers = {
  Query: {
    formattedToday() {
      return moment().format('DD.MM.YYYY')
    }
  }
}
```

```ts filename="gateway.config.ts"
import { defineConfig } from '@graphql-hive/gateway'
import { additionalResolvers } from './my-time'

export const gatewayConfig = defineConfig({ additionalResolvers })
```

Your Dockerfile should then look something like this:

```dockerfile filename="Dockerfile"
FROM ghcr.io/ardatan/hive-gateway

# we dont install dev deps because:
#   1. we need them for type checking only
#   2. Hive Gateway is already available in the docker image
COPY package*.json .
RUN npm i --omit=dev

COPY my-time.ts .
COPY gateway.config.ts .
```

Then build your image:

```sh
docker build -t hive-gateway-w-add-res .
```

And finally start it (the config file is in the image and doesn't need to be mounted):

```sh
docker run -p 4000:4000 hive-gateway-w-add-res supergraph
```
