Host a Godot or Unity web build

Your web export online with the headers it needs, on the free plan.

GitHub Pages cannot set the response headers a game engine asks for. A threaded Godot 4 export refuses to start there, and a Unity Brotli or Gzip build downloads as unreadable bytes. The game-web-starter template fixes that with two small files in your export folder, a Dockerfile and a Caddyfile. This template is not affiliated with Godot or Unity.

Why the headers matter

  • Godot threads: a Godot 4 export with Thread Support only runs when the page is cross-origin isolated. The server sends Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp on every path.
  • Unity Brotli and Gzip: the browser can only unpack .br and .gz files when the server sends Content-Encoding and the type of the file inside. The server does that for the names Unity writes.
  • File types: .wasm is sent as application/wasm and .pck as application/octet-stream. Every file is sent with Cache-Control: no-cache, so a new build shows up on the next load and unchanged files cost one quick check.

The build compresses your game once, when the app is built, so every player gets the small download and the running app does no compression work. Each player gets the Brotli or Gzip copy their browser accepts, and the original if it accepts neither. A file that is already Brotli or Gzip is left alone.

Journey 1: deploy the button, then use your own build

  1. Open the deploy link and sign in if asked. Under App size, keep 256 MB. The free plan is enough.
  2. Click Deploy and wait until the app shows as running. Open its address and play Meteor Run, a small Godot 4 arcade game: steer with the arrow keys or by dragging, shoot the meteors. The corner says Cross-origin isolated: yes and Threads: on, which is what the headers make possible.
  3. Click Use this template on GitHub to make your own copy, connect it in Dockhold, and deploy it.
  4. In your copy, replace the demo files at the root with your export. Keep Dockerfile, Caddyfile, .dockerignore and the GODOT-LICENSE.txt and GODOT-COPYRIGHT.txt notices. You can delete demo-src/.
  5. Commit and push. Every push to your main branch redeploys the app.

Journey 2: no git

  1. Export your game into a folder. The entry page must be named index.html.
  2. Copy the template's Dockerfile and Caddyfile into that folder, next to index.html. They are the only two files you add.
  3. From that folder, run:
    npx dockhold login
    npx dockhold deploy
    login opens a browser once. deploy uploads the folder, compresses it while building, and prints the address of your app. Run npx dockhold deploy again to ship a new build.

The entry page must be index.html

The server serves your export from its root, and the page people open is index.html.

  • Godot: in Project > Export, choose the Web preset and, when asked for the file name, use index.html. Godot writes index.js, index.wasm, index.pck and the other files next to it.
  • Unity: a WebGL build writes index.html and a Build/ folder. Use the build output folder as it is.

Unity notes

Build the WebGL player with Compression Format set to Brotli or Gzip, and leave Decompression Fallback off. The .br and .gz files, including .symbols.json.br, work as built. Unity needs no change to the build.

Turn cross-origin isolation off

Isolation is what lets a threaded Godot build run. It also stops the page from embedding third-party content that does not opt in, such as some ad or video frames. If your page embeds such content and your game does not use threads, open the app's Variables tab, add CROSS_ORIGIN_ISOLATION with the value off (exactly that, lowercase), and restart the app. The two headers are gone from every response, and the file type and encoding rules stay. Remove the variable to turn isolation back on.

Options

The free app is the full template. With a paid plan you can also add:

  • A private playtest link. Make the app private on its Access tab. Visitors see a browser login prompt before the game loads, and you give out a token as the password.
  • Your own domain. Add a custom domain on the app's Domains tab, so the game lives on your own site's address.

Troubleshooting

  • A threaded Godot build does not start: it needs the page to be cross-origin isolated. Check that CROSS_ORIGIN_ISOLATION is not set to off on the Variables tab.
  • A page embeds an ad or video frame that stops loading: isolation blocks third-party content that does not opt in. See the section above.

Next

Suggested guides
↑↓ to move ↵ to open Esc to close From the Dockhold docs