Skip to main content

Export and Containerize

What You Will Learn

  • What files a Godot HTML5 export produces and why each one matters
  • How to write a Dockerfile that correctly serves a Godot web export
  • How to build a multi-architecture image and push it to ECR

Step 1: Export from Godot

You already know how to do this, so here is the quick version:

  1. Open your project in Godot
  2. Go to Project > Export
  3. Select Web (or add it if it is not there yet)
  4. Set the export path to a folder called export/ inside your project
  5. Click Export Project (uncheck "Export With Debug" for a smaller build)

What the Export Produces

After exporting, your export/ folder will contain these files:

File Purpose
index.html The HTML page that loads the game engine
index.js JavaScript glue code that initializes the WebAssembly runtime
index.wasm The compiled game engine (WebAssembly binary)
index.pck Your game's packed resources (scenes, scripts, assets)
index.png The default favicon/icon
index.apple-touch-icon.png iOS home screen icon

These are all static files. To deploy them, you just need a web server that can serve them with the correct headers.


Step 2: The Dockerfile

Godot HTML5 exports need specific HTTP headers to work. The game engine uses SharedArrayBuffer, which browsers only allow when the server sends the right cross-origin isolation headers. Without them, your game will fail to load with a cryptic error in the console.

Here is the complete Dockerfile:

# Use nginx:alpine as the base image
# Alpine keeps the image small (~40MB vs ~140MB for full nginx)
FROM nginx:alpine

# Remove the default nginx welcome page
RUN rm -rf /usr/share/nginx/html/*

# Copy the Godot export files into the nginx web root
COPY export/ /usr/share/nginx/html/

# Create a custom nginx config that:
# 1. Sets correct MIME types for .wasm and .pck files
# 2. Adds cross-origin isolation headers required by SharedArrayBuffer
RUN cat > /etc/nginx/conf.d/default.conf << 'EOF'
server {
    listen 80;
    server_name _;
    root /usr/share/nginx/html;
    index index.html;

    # Correct MIME types for Godot export files
    types {
        application/wasm wasm;
        application/octet-stream pck;
    }

    # Cross-origin isolation headers required for SharedArrayBuffer
    # Without these, the Godot engine will refuse to start
    add_header Cross-Origin-Opener-Policy "same-origin" always;
    add_header Cross-Origin-Embedder-Policy "require-corp" always;

    location / {
        try_files $uri $uri/ =404;
    }
}
EOF

# Expose port 80 (nginx default)
EXPOSE 80

# nginx:alpine already has CMD ["nginx", "-g", "daemon off;"]
# so we do not need to specify it again

Save this file as Dockerfile in the root of your Godot project (next to the export/ folder).

Why These Headers Matter

Header What It Does
Cross-Origin-Opener-Policy: same-origin Isolates the browsing context so SharedArrayBuffer is allowed
Cross-Origin-Embedder-Policy: require-corp Ensures all loaded resources are explicitly CORS-opted-in

Without both headers, modern browsers (Chrome 92+, Firefox 79+) will block SharedArrayBuffer and your game will not load.


Step 3: Build the Multi-Architecture Image

The Junovy cluster runs exclusively on ARM64 nodes. However, you likely develop on an x86 (amd64) machine. Building multi-architecture images ensures your containers work both locally and on the cluster. Docker Buildx handles this for you.

Set Up Buildx (One-Time)

# Create a new builder that supports multi-platform builds
docker buildx create --name junovy-builder --use

# Verify it supports both platforms
docker buildx inspect --bootstrap

Authenticate with ECR

# Log in to the Junovy ECR registry
aws ecr get-login-password --region eu-central-1 | \
  docker login --username AWS --password-stdin \
  <account-id>.dkr.ecr.eu-central-1.amazonaws.com

Replace <account-id> with the actual AWS account ID (ask your team lead if you do not have it).

Build and Push

# Build for both architectures and push directly to ECR
# --platform: target architectures (required by Junovy)
# --tag: the full ECR image path with version tag
# --push: build and push in one step (multi-arch can't use --load)
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  --tag <account-id>.dkr.ecr.eu-central-1.amazonaws.com/godot-demo:v1.0.0 \
  --push \
  .

Verify the Push

# List the tags in the ECR repository to confirm it arrived
aws ecr describe-images \
  --repository-name godot-demo \
  --region eu-central-1 \
  --query 'imageDetails[*].imageTags' \
  --output table

You should see v1.0.0 in the output.


Your Project Structure So Far

At this point, your local Godot project directory should look like this:

my-godot-game/
  project.godot          # Your Godot project file
  Dockerfile             # The Dockerfile you just created
  export/                # The HTML5 export output
    index.html
    index.js
    index.wasm
    index.pck
    index.png
    index.apple-touch-icon.png
  scenes/                # Your game scenes (not deployed)
  scripts/               # Your game scripts (not deployed)

Only the export/ folder goes into the container. Your source code stays local.


Key Takeaways

  • Godot HTML5 exports produce static files (HTML, JS, WASM, PCK) that need a web server
  • The Dockerfile uses nginx:alpine and adds headers for SharedArrayBuffer support
  • Without cross-origin isolation headers, the game will not load in modern browsers
  • The cluster runs ARM64 nodes, so images must include linux/arm64 (plus linux/amd64 for local testing)
  • The image is pushed to the Junovy AWS ECR registry in eu-central-1

What Is Next

Next up: Create the Tenant where you will set up the namespace and directory structure in the Flux repo so the cluster knows where to put your game.