Skip to main content

Run moley in Docker

A pre-built container is published to the GitHub Container Registry at ghcr.io/stupside/moley. The image bundles both moley and a pinned cloudflared on Alpine — nothing else to install on the host.

Why containerize

  • No local Go / Homebrew / cloudflared setup — the image has everything.
  • Pinned cloudflared version — reproducible across laptops and CI.
  • Isolated state~/.moley lives in a volume you control.
  • Configure via env vars — no moley.yml required for the common case.

Quick start with Compose

The repo ships a ready-to-use docker-compose.yml:

docker-compose.yml
services:
moley:
image: ghcr.io/stupside/moley:latest
container_name: moley
environment:
MOLEY_CLOUDFLARE__TOKEN: "<your-cloudflare-api-token>"

MOLEY_TUNNEL_TUNNEL__NAME: "moley"
MOLEY_TUNNEL_TUNNEL__PERSISTENT: "false"

MOLEY_TUNNEL_INGRESS__ZONE: "<your-zone.com>"
MOLEY_TUNNEL_INGRESS__MODE: "subdomain"

MOLEY_TUNNEL_INGRESS__APPS__0__TARGET__HOSTNAME: "host.docker.internal"
MOLEY_TUNNEL_INGRESS__APPS__0__TARGET__PORT: "3000"
MOLEY_TUNNEL_INGRESS__APPS__0__TARGET__PROTOCOL: "http"
MOLEY_TUNNEL_INGRESS__APPS__0__EXPOSE__SUBDOMAIN: "app"
volumes:
- ./data/.moley:/root/.moley
command: ["tunnel", "run"]

Edit the two <…> placeholders, then run:

docker compose up

Ctrl-C to stop. Moley cleans up the tunnel, DNS records, and Access apps on shutdown.

tip

State is persisted in ./data/.moley/ on the host. Delete that folder to start from a clean slate.

Adding more apps

Each app is a numbered block under APPS__N__. To expose a second service, duplicate the four APPS__0__ lines as APPS__1__:

MOLEY_TUNNEL_INGRESS__APPS__1__TARGET__HOSTNAME: "host.docker.internal"
MOLEY_TUNNEL_INGRESS__APPS__1__TARGET__PORT: "8080"
MOLEY_TUNNEL_INGRESS__APPS__1__TARGET__PROTOCOL: "http"
MOLEY_TUNNEL_INGRESS__APPS__1__EXPOSE__SUBDOMAIN: "api"

Indices must be sequential (0, 1, 2, …) — gaps are ignored.

Talking to services on the host

Inside the container, localhost is the container itself — not your Mac/Linux host. Use the platform-specific alias:

host.docker.internal resolves to the host (default in the snippet above):

MOLEY_TUNNEL_INGRESS__APPS__0__TARGET__HOSTNAME: "host.docker.internal"

Running another stack in the same compose file

Moley plays nicely with the apps it exposes. Put them side-by-side and reach the app by its Compose service name:

docker-compose.yml
services:
web:
build: ./web
ports:
- "3000:3000"

moley:
image: ghcr.io/stupside/moley:latest
depends_on:
- web
environment:
MOLEY_CLOUDFLARE__TOKEN: "<your-cloudflare-api-token>"
MOLEY_TUNNEL_INGRESS__ZONE: "<your-zone.com>"
MOLEY_TUNNEL_INGRESS__MODE: "subdomain"
MOLEY_TUNNEL_INGRESS__APPS__0__TARGET__HOSTNAME: "web"
MOLEY_TUNNEL_INGRESS__APPS__0__TARGET__PORT: "3000"
MOLEY_TUNNEL_INGRESS__APPS__0__TARGET__PROTOCOL: "http"
MOLEY_TUNNEL_INGRESS__APPS__0__EXPOSE__SUBDOMAIN: "app"
volumes:
- ./data/.moley:/root/.moley
command: ["tunnel", "run"]
info

cloudflared inside moley reaches any other service on the Compose network by its container name — no host.docker.internal needed.

Using a moley.yml file instead

Env vars cover the common case. For advanced setups — multiple Access policies, per-app Access providers, custom session durations — a moley.yml file is more readable. Mount it read-only and drop the matching env vars:

docker-compose.yml
services:
moley:
image: ghcr.io/stupside/moley:latest
environment:
# Token is the only env var still required
MOLEY_CLOUDFLARE__TOKEN: "<your-cloudflare-api-token>"
volumes:
- ./moley.yml:/root/moley.yml:ro
- ./data/.moley:/root/.moley
command: ["tunnel", "run"]

Generate a starter moley.yml on the host with moley tunnel init (run the binary directly or via docker compose run --rm moley tunnel init against a writable mount).

Env vars and moley.yml can also be combined — env vars override file values key-by-key. See Configuration → Environment variables for the full convention.

Taskfile shortcuts

If you already use Task, the project's Taskfile.yml wraps these into two reusable tasks:

# Run any moley command inside the container
task docker:moley -- tunnel run
task docker:moley -- tunnel stop

# Run cloudflared directly (for manual debugging)
task docker:cloudflared -- tunnel list
task docker:cloudflared -- --version