Skip to content

Installation

60plus edited this page Jul 7, 2026 · 12 revisions

Installation

GamesDownloader ships as a set of Docker containers orchestrated by Docker Compose: the application itself, a MariaDB database, and a Redis-compatible cache/queue (Valkey). This page takes you from nothing to a running instance, explains every setting in the .env file, and covers first boot, updating, and resetting.

Just want the short version? Copy .env.example to .env, change the four passwords, run docker compose up -d, and open http://localhost:8080. Everything below is the detail behind those four steps.


Contents


Requirements

Requirement Notes
Docker Engine 24+ recommended. Install guide.
Docker Compose v2 (the docker compose subcommand, not the legacy docker-compose).
Disk The app image plus MariaDB and Valkey are modest, but your library, ROMs, downloads, and artwork can grow to hundreds of GB. Point GD_BASE_DIR at a roomy disk. First boot also pulls a ~150 MB LaunchBox metadata file and (optionally) ~300 MB of ClamAV virus definitions.
RAM 2 GB is enough for a small library; 4 GB+ is comfortable once scraping and emulation are in use.
Architecture Prebuilt images are published for linux/amd64 and linux/arm64 (Raspberry Pi 4/5, Apple Silicon under Linux VMs, etc.).
Ports 8080 (web UI) is required. 9091 (Transmission WebUI) and 51413/tcp+udp (torrent peers) are only needed if you use torrents.

GamesDownloader is designed to run happily LAN-only with no reverse proxy. HTTPS, a domain, and a proxy are optional and only relevant if you expose it beyond your local network - see Reverse Proxy & HTTPS.


Step 1: Get the files

You need two files: docker-compose.yml and your .env. The easiest way is to clone the repository:

git clone https://github.com/60plus/GamesDownloader.git
cd GamesDownloader
cp .env.example .env

The application container is fully self-contained. A single image bundles everything the app needs: the FastAPI backend, the compiled Vue frontend, a self-hosted copy of EmulatorJS (so in-browser emulation works with no external CDN), the ClamAV antivirus daemon, the Transmission torrent daemon, ffmpeg, and a Node.js + Vite toolchain used to compile theme-plugin .vue files at startup. You do not install any of those separately. Only MariaDB and Valkey run as their own containers.

There are two ways to obtain that image. Most people should use Option A.

Option A - prebuilt image from Docker Hub (recommended)

No local build, no Node or Python toolchain on your host, no wait. Multi-arch images (linux/amd64 and linux/arm64) are published to Docker Hub automatically for every release. Docker pulls the right architecture for your machine from the same tag.

1. Point the compose file at the image. Open docker-compose.yml and change the app service so it uses image: instead of building. Replace this:

  gamesdownloader:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: GamesDownloader
    # ... the rest of the service stays exactly as-is ...

with this (only the top of the service changes; keep container_name, environment, ports, volumes, depends_on, healthcheck untouched):

  gamesdownloader:
    image: 60plus/gamesdownloader:1.0.16
    container_name: GamesDownloader
    # ... the rest of the service stays exactly as-is ...

2. Pull and start:

docker compose pull gamesdownloader
docker compose up -d

Which tag should I use?

Tag Example Behaviour Use when
latest 60plus/gamesdownloader:latest Always the newest release. Moves forward every time you pull. You want the newest version and are happy to upgrade on each pull.
MAJOR.MINOR 60plus/gamesdownloader:1.0 Rolling within a minor line; picks up patch releases (1.0.16, 1.0.17, ...) but not a future 1.1. You want automatic patch/bugfix updates but a stable feature set.
MAJOR.MINOR.PATCH 60plus/gamesdownloader:1.0.16 Frozen to one exact build. Never changes. Recommended for most self-hosters - you decide when to move up, after reading the release notes.

For maximum reproducibility you can pin by digest, which is immune even to a tag being re-pushed:

docker pull 60plus/gamesdownloader:1.0.16
docker inspect --format='{{index .RepoDigests 0}}' 60plus/gamesdownloader:1.0.16
# -> 60plus/gamesdownloader@sha256:....  put that in image:

Check which version an image is once it is running:

curl http://localhost:8080/api/health      # {"status":"ok","version":"1.0.16"}

Note on PUID/PGID: the prebuilt image honours these exactly like a source build - the entrypoint drops privileges at start. Nothing extra is needed.

Option B - build from source

Building locally produces the same image from the code in the repository. Choose this when you want to modify the code, run on a machine that cannot reach Docker Hub (air-gapped or restricted networks), audit exactly what goes into the image, or develop your own plugins/themes against a local build.

1. Leave the build: block in docker-compose.yml as it ships (the default). Then:

docker compose build gamesdownloader
docker compose up -d

What the build does. It is a multi-stage Docker build, so the final image stays lean:

  1. Frontend stage (node:22-alpine) - runs npm ci and npm run build to compile the Vue 3 + Vite single-page app into static files.
  2. EmulatorJS stage (alpine:3.20) - downloads a pinned, checksum-verified EmulatorJS release and unpacks it, so emulation is served from your own server rather than a third-party CDN.
  3. Backend stage (python:3.13-slim) - installs system packages (ClamAV, Transmission, ffmpeg, Node.js for the plugin compiler), installs the Python requirements, then copies in the backend code, the compiled frontend, and EmulatorJS.

How long / how much. Expect a few minutes on a typical machine (dominated by the npm install and the Python wheel install). Subsequent builds are much faster because Docker caches the dependency layers; only the layers after your changed files rebuild. Give the Docker daemon at least ~2 GB of free RAM and a few GB of disk for the build cache.

Rebuilding after you pull code changes:

git pull
docker compose build gamesdownloader
docker compose up -d gamesdownloader

Optional build arguments. The EmulatorJS version is pinned in the Dockerfile via build args (EMULATORJS_VERSION, EMULATORJS_SHA256); override them only if you know you need a different EmulatorJS release and have the matching checksum.

Multi-architecture builds. A plain docker compose build produces an image for your host's architecture only. To build for another platform (for example an arm64 image on an amd64 machine), use Docker Buildx:

docker buildx build --platform linux/arm64 -t gamesdownloader:arm64 .

If a from-source build fails with an odd decompression or segmentation error partway through, it is almost always a corrupted Docker layer cache rather than a code problem. See Troubleshooting & FAQ for the clean-rebuild procedure.

Which should I choose?

Option A (Docker Hub) Option B (build from source)
Setup speed Fast (pull only) Slower (compiles frontend + installs deps)
Host toolchain None Docker with enough RAM/disk for the build
Needs internet to Docker Hub Yes No (once the base images are cached)
Code modifications No Yes
Recommended for Almost everyone Contributors, air-gapped hosts, auditors

Step 2: Configure .env

Open .env. At minimum, change every password before the instance is reachable by anyone but you. Here is every variable:

Paths and ports

Variable Default What it does
GD_BASE_DIR /APPS/GamesDownloader Host directory under which all persistent data is stored. Point this at a large disk.
GD_PORT 8080 Host port mapped to the container's web UI.
PUID / PGID empty (root) Optional. Run the container's processes as a specific user/group so files on the host are owned by you, not root. See below.
GD_DEBUG false Verbose logging and relaxed checks. Never enable in production - among other things it downgrades the secret-key safety guard.

Database (MariaDB)

Variable Default What it does
DB_NAME gamesdownloader Database name.
DB_USER gd Application database user.
DB_PASSWD change-me-strong-random Password for DB_USER. Change it.
DB_ROOT_PASSWD change-me-root-strong-random MariaDB root password. Change it.

Important: DB_ROOT_PASSWD is applied once, on the very first start of the MariaDB container, and baked into its data volume. Changing it in .env later does not change it in an existing database. Set a strong value before the first start.

Generate strong values with:

openssl rand -hex 24

Cache / queue (Redis / Valkey)

Variable Default What it does
REDIS_PASSWORD change-me-redis-strong-random Password for the Valkey server (used for sessions, brute-force counters, job queue). Change it.

Auth secret key

Variable Default What it does
GD_AUTH_SECRET_KEY change-me-in-production Signs JWT session tokens and derives the key that encrypts secrets at rest (SMTP password, OAuth client secrets, scraper API keys).

Behaviour:

  • If you leave it at change-me-in-production or empty, the container auto-generates a 256-bit key on first start and saves it to /data/config/.secret_key (inside a mounted volume, so it survives rebuilds). This is the recommended path for most users.
  • If you set your own, use a long random string: openssl rand -hex 32.
  • Keep /data/config/.secret_key safe. Losing it invalidates every session and makes encrypted-at-rest secrets unrecoverable (you would have to re-enter your scraper keys and SMTP password).
  • A known-weak or empty key is refused at startup - the server will not sign tokens with an insecure secret.

Metadata API keys (all optional)

These can be left blank and configured later in the setup wizard or Settings → Metadata. They are stored encrypted in the database; the env vars are only a convenience for pre-seeding.

Variable Service
IGDB_CLIENT_ID, IGDB_CLIENT_SECRET IGDB (Twitch developer app)
RAWG_API_KEY RAWG.io
STEAMGRIDDB_API_KEY SteamGridDB
SCREENSCRAPER_USERNAME, SCREENSCRAPER_PASSWORD ScreenScraper.fr
RA_API_KEY RetroAchievements (optional)

See Scrapers & Metadata for where to obtain each key and how title matching works.

Optional token expiry

Uncomment in .env to override defaults:

# GD_TOKEN_EXPIRE_MIN=60      # access-token lifetime in minutes
# GD_REFRESH_EXPIRE_DAYS=7    # refresh-token lifetime in days

Step 3: Start the stack

docker compose up -d

This starts three containers:

Container Image Role
GamesDownloader built or 60plus/gamesdownloader The application (web UI, API, workers, ClamAV, Transmission).
gd-mariadb mariadb:11.3 Database.
gd-redis valkey/valkey:8 Cache, sessions, brute-force counters, job queue.

The app waits for MariaDB and Valkey to report healthy before it starts (Compose depends_on with health conditions), so a clean up may take a moment before the app begins initialising.


First boot: what happens

The first start does more work than later ones, and it is normal for the web UI to be unavailable for 60-120 seconds:

  1. The database schema is created and migrated.
  2. The app downloads the LaunchBox metadata archive (~150 MB) and parses it into an on-disk SQLite index (used for ROM cover art and metadata). This happens before the API binds.
  3. ROM platform directories are created under data/games/roms/.
  4. If GD_AUTH_SECRET_KEY was left at its default, a secret key is generated and written to data/config/.secret_key.

Because of the LaunchBox step, the container's health check uses a 120 second start_period - the orchestrator will not mark it unhealthy during that window. If you watch docker compose logs -f gamesdownloader you will see the metadata parse progress, then a line indicating the API is listening on port 8080.


The setup wizard

Open http://localhost:8080 (or http://<server-ip>:8080 from another device on your LAN). On first run you are greeted by a 7-step setup wizard, no shell access required:

  1. Welcome - pick your interface language (8 available).
  2. Admin account - create the first administrator (username, email, password).
  3. GOG account - optionally connect a GOG account for library sync (can be done later in Profile).
  4. Scrapers - enter IGDB, RAWG, ScreenScraper, SteamGridDB keys (all optional, changeable later).
  5. Preferences - default library view, theme, and related options.
  6. Review - confirm your choices.
  7. Finish - the wizard writes settings and drops you into the app.

Everything chosen here can be changed afterwards in Settings. The wizard only appears while no admin account exists.


Post-install steps

A few features need a one-time nudge after install:

  • ClamAV antivirus (optional): go to Settings → Security → ClamAV and click Update Definitions to download the virus database (~300 MB). The ClamAV daemon starts automatically once definitions are present. Until then, upload/download scanning is simply inactive - the app runs fine without it.
  • Transmission / torrents (optional): the Transmission daemon starts inside the container automatically. Enable it in Settings → Downloads → Transmission. If you want inbound peer connections, expose ports 9091 (WebUI) and 51413 (peers) through your firewall.
  • ROMs: place ROM files in GD_BASE_DIR/data/games/roms/{platform}/ (for example roms/snes/, roms/n64/), then use Scan ROMs in the Emulation library. See ROMs & Emulation.

Where your data lives

Everything persistent sits under GD_BASE_DIR on the host. The most important directories:

Path Contents
data/games/GOG Published GOG game files
data/games/CUSTOM Custom game files (uploaded, torrent, scanned)
data/games/roms/{platform} ROM files per platform
data/downloads GOG installer staging (pre-publish)
data/resources/... Downloaded artwork, save states, avatars, platform art
data/config settings.yaml, the .secret_key, LaunchBox index, Transmission config
data/plugins Installed plugins, one subdirectory each
data/clamav ClamAV virus definitions
data/db MariaDB data files
data/redis Valkey persistence

The full volume table (including per-user save-state and media paths) is on the Configuration page. Back up data/db and data/config at minimum - the database plus the secret key are what make your library and encrypted settings recoverable.


Running as a non-root user (PUID/PGID)

By default the container runs as root, so files it writes on the host are root-owned. To have them owned by your own user, find your IDs on the host:

id -u    # -> PUID
id -g    # -> PGID

Put those into .env:

PUID=1000
PGID=1000

Then recreate the app container (docker compose up -d). New files will be owned by that user/group. If you switch this on an existing install, you may need to chown -R the GD_BASE_DIR once so previously root-owned files are readable.


Updating

If you use the prebuilt image (Option A)

docker compose pull gamesdownloader
docker compose up -d gamesdownloader

If you build from source (Option B)

git pull
docker compose build gamesdownloader
docker compose up -d gamesdownloader

Database schema migrations run automatically on start. Your data volumes are untouched by an image rebuild. It is good practice to back up data/db before a major upgrade.

Tip: pin a specific image tag (e.g. 60plus/gamesdownloader:1.0.16) instead of latest if you prefer to review release notes before each upgrade.


Verifying the install

The app exposes a health endpoint that also reports the running version:

curl http://localhost:8080/api/health
# {"status":"ok","version":"1.0.16"}

Check container status and logs:

docker compose ps
docker compose logs -f gamesdownloader

A healthy stack shows all three containers Up and the app (healthy) once the first-boot window has passed.


Resetting / uninstalling

To stop the stack but keep your data:

docker compose down

To remove the containers and delete all data (this is destructive and irreversible):

docker compose down
rm -rf "$GD_BASE_DIR"     # deletes the database, library, artwork, everything

To reset just the application state (fresh setup wizard) while keeping your game files, remove data/db and data/redis but leave data/games in place. Note that this also discards users, settings, and scraped metadata.


Next: Configuration for the full settings tour, or Reverse Proxy & HTTPS if you plan to expose the instance beyond your LAN.

Clone this wiki locally