> ## Documentation Index
> Fetch the complete documentation index at: https://docs.playflowcloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Docker Image Builds

> Deploy game servers from your own Docker images.

# Docker Image Builds

PlayFlow supports two ways to deploy your game server: uploading a ZIP of your server build, or importing a pre-built Docker image. This guide covers the Docker image approach, which gives you full control over your server environment and enables tighter CI/CD integration.

## How It Works

When you import a Docker image, PlayFlow pulls it from your container registry and makes it available for server launches. Servers started from a Docker image build skip the ZIP download and extraction step entirely -- the image is ready to run immediately.

<Info>
  Docker image builds are ideal for teams that already have a containerized build pipeline. If you are new to containers or prefer a simpler workflow, the [ZIP upload method](/unity/game-servers) handles containerization for you automatically.
</Info>

## Prerequisites

Before you begin, make sure you have:

* A PlayFlow project with a **server API key** (the `pf_`-prefixed key from **Project Settings** on the dashboard). Keep it server-side — in CI, store it as a secret. Client keys (`pfclient_`) are meant for game builds and cannot manage builds.
* A Docker image pushed to an accessible container registry (Docker Hub, GitHub Container Registry, AWS ECR, etc.)
* If your registry is private, the credentials to pull the image

## Importing a Docker Image

<Steps>
  <Step title="Push your image to a container registry">
    Build and push your game server image to any Docker-compatible registry. Make sure the image contains everything your server needs to run.

    ```bash theme={null}
    docker build -t registry.example.com/my-game-server:v1.0 .
    docker push registry.example.com/my-game-server:v1.0
    ```
  </Step>

  <Step title="Import the image into PlayFlow">
    Call the Docker image import endpoint with your image URL and, optionally, registry credentials and an executable path.

    <CodeGroup>
      ```bash curl (public registry) theme={null}
      curl -X POST https://api.computeflow.cloud/api/v3/builds/docker-image \
        -H "api-key: YOUR_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "name": "my-server-build",
          "image_url": "registry.example.com/my-game-server:v1.0"
        }'
      ```

      ```bash curl (private registry) theme={null}
      curl -X POST https://api.computeflow.cloud/api/v3/builds/docker-image \
        -H "api-key: YOUR_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "name": "my-server-build",
          "image_url": "registry.example.com/my-game-server:v1.0",
          "executable_path": "/app/server",
          "registry_credentials": {
            "username": "your-username",
            "password": "your-access-token"
          }
        }'
      ```
    </CodeGroup>
  </Step>

  <Step title="Wait for processing to complete">
    The build is created with status `processing`. PlayFlow pulls the image and prepares it for server launches. You can poll the build status endpoint or watch for updates on the dashboard.

    ```bash theme={null}
    curl https://api.computeflow.cloud/api/v3/builds/BUILD_ID \
      -H "api-key: YOUR_API_KEY"
    ```

    When processing finishes, the status changes to `ready`.
  </Step>

  <Step title="Launch a server from the build">
    Once the build is ready, start a server using it just like any other build. PlayFlow uses the Docker image directly -- no ZIP download or extraction required.

    You reference a build **by the name you gave it at import**, not by its ID. Pass that name as `version_tag`, and always include a `name` and `region` (both required):

    ```bash theme={null}
    curl -X POST https://api.computeflow.cloud/api/v3/servers/start \
      -H "api-key: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "my-match-1",
        "region": "us-east",
        "version_tag": "my-server-build"
      }'
    ```

    <Info>
      `version_tag` is the build name you chose when importing (here, `my-server-build`) — PlayFlow launches the latest `ready` build with that name. To pin an exact build version, also pass `version` (a number). The `BUILD_ID` you polled in the previous step is only for checking build status; it is not used to launch a server.
    </Info>

    <Tip>
      Read the connection host and port from the `network_ports[]` array in the start response (each entry has `host` and `external_port`) rather than assuming a fixed port — PlayFlow allocates the external port dynamically, and it differs from your internal port.
    </Tip>
  </Step>
</Steps>

## Request Body Reference

| Field                           | Type   | Required                      | Description                                                                                                                                              |
| :------------------------------ | :----- | :---------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`                          | string | No (defaults to `"default"`)  | The name for this build — this is what you pass as `version_tag` when launching a server. Builds with the same name auto-increment their version number. |
| `image_url`                     | string | Yes                           | The full image URL including tag (e.g., `registry.example.com/my-server:v1.0`).                                                                          |
| `executable_path`               | string | No                            | The path to the server executable inside the container. Only needed if it cannot be inferred automatically.                                              |
| `registry_credentials`          | object | No                            | Credentials for pulling from a private registry.                                                                                                         |
| `registry_credentials.username` | string | Yes (if credentials provided) | Registry username or service account name.                                                                                                               |
| `registry_credentials.password` | string | Yes (if credentials provided) | Registry password, access token, or personal access token.                                                                                               |

## Docker Image vs. ZIP Upload

<Tabs>
  <Tab title="Docker Image">
    **Best for:** advanced users, custom runtimes, CI/CD pipelines, non-standard server setups.

    * You build and manage the Docker image yourself
    * Full control over the base image, dependencies, and runtime environment
    * Faster server startup since the image is pre-built
    * Integrates naturally with CI/CD -- push to your registry, then import to PlayFlow
    * Supports any language, framework, or game engine
  </Tab>

  <Tab title="ZIP Upload">
    **Best for:** simple deployments, Unity/Godot builds, getting started quickly.

    * Upload a ZIP of your server binary and PlayFlow handles containerization
    * No Docker knowledge required
    * Great for standard Unity or Godot dedicated server builds
    * PlayFlow manages the image build and optimization
  </Tab>
</Tabs>

## CI/CD Integration

Docker image builds fit naturally into automated pipelines. Here is an example GitHub Actions workflow that builds your server image, pushes it to a registry, and imports it into PlayFlow:

```yaml github-actions.yml theme={null}
name: Deploy Game Server
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build and push Docker image
        run: |
          docker build -t ghcr.io/${{ github.repository }}/server:${{ github.sha }} .
          echo "${{ secrets.GHCR_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
          docker push ghcr.io/${{ github.repository }}/server:${{ github.sha }}

      - name: Import to PlayFlow
        run: |
          curl -X POST https://api.computeflow.cloud/api/v3/builds/docker-image \
            -H "api-key: ${{ secrets.PLAYFLOW_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d '{
              "name": "production-server",
              "image_url": "ghcr.io/${{ github.repository }}/server:${{ github.sha }}",
              "registry_credentials": {
                "username": "${{ github.actor }}",
                "password": "${{ secrets.GHCR_TOKEN }}"
              }
            }'
```

## Tips

<Warning>
  Always use a specific image tag (e.g., `:v1.0` or a commit SHA) rather than `:latest`. This ensures that your servers run the exact version you tested, and makes rollbacks straightforward.
</Warning>

* **Keep images small.** Smaller images pull faster, which reduces build processing time. Use multi-stage builds and minimal base images (e.g., `debian-slim`, `alpine`) where possible.
* **Signal readiness from your server.** PlayFlow does not probe a custom health endpoint in your game. Server readiness is reported by the PlayFlow agent sidecar via a status callback once your game process is up, so make sure your server starts and binds its ports promptly.
* **Test locally first.** Run your image with `docker run` before importing to PlayFlow to catch configuration issues early.

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Server Regions" icon="globe" href="/fundamentals/regions">
    Choose the best deployment region for your players.
  </Card>

  <Card title="Pricing & Instance Types" icon="credit-card" href="/fundamentals/plan-instance-types">
    Review available compute sizes and pricing.
  </Card>
</CardGroup>
