Skip to main content
Version: 1.5

Installation

All you need on the host is Docker with the Compose plugin. Fadebox runs as two containers: the app and a PostgreSQL database.

1. Create the compose file​

Save this as docker-compose.yml:

# Fadebox — app + PostgreSQL. Create .env next to this file first (step 2).
#
# Nothing host-specific is baked in here: the Docker socket path and the GID that owns it
# come from .env, because they differ per host (docker group on Linux, root on Docker
# Desktop, your own uid under rootless Docker).
services:
db:
image: postgres:18
environment:
POSTGRES_DB: fadebox
POSTGRES_USER: fadebox
POSTGRES_PASSWORD: ${DB_PASSWORD:?set DB_PASSWORD in .env}
# No published port: the app reaches it as db:5432 on the compose network, and a published
# port bypasses the host firewall (ufw, firewalld). For a shell or a dump, go through the
# container: `docker compose exec db psql -U fadebox fadebox`.
volumes:
# PG18 images store data in a version-specific subdir under /var/lib/postgresql
- fadebox-db:/var/lib/postgresql
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U fadebox -d fadebox" ]
interval: 5s
timeout: 5s
retries: 10

app:
image: fadebox/fadebox:1.5
depends_on:
db:
condition: service_healthy
# The image's own uid (185, UBI convention); only the supplementary group varies per host.
user: "${FADEBOX_UID:-185}:${FADEBOX_GID:-0}"
group_add:
# GID that owns the Docker socket — see step 2 for how to find it.
- "${DOCKER_GID:?set DOCKER_GID in .env}"
# On an SELinux-enforcing host (Fedora, RHEL, openSUSE with the daemon's SELinux integration
# on) a confined container cannot open the bind-mounted socket whatever its uid and gid, and
# every deploy fails with "permission denied". A no-op anywhere else.
security_opt:
- label=disable
environment:
# Point the baked-in postgres datasource at the db service.
QUARKUS_DATASOURCE_JDBC_URL: jdbc:postgresql://db:5432/fadebox
QUARKUS_DATASOURCE_USERNAME: fadebox
QUARKUS_DATASOURCE_PASSWORD: ${DB_PASSWORD:?set DB_PASSWORD in .env}
# Session-cookie encryption key; generate with `openssl rand -base64 32`.
QUARKUS_HTTP_AUTH_SESSION_ENCRYPTION_KEY: "${FADEBOX_SESSION_KEY:?set FADEBOX_SESSION_KEY in .env}"
# At-rest encryption key for stored credentials; generate the same way. Keep it stable —
# changing it makes every stored credential unreadable until re-entered.
FADEBOX_ENCRYPTION_KEY: "${FADEBOX_ENCRYPTION_KEY:?set FADEBOX_ENCRYPTION_KEY in .env}"
# Bare-clone cache for git value sources (kept on a volume so fetches stay incremental)
FADEBOX_GIT_CACHE_DIR: /data/git
# Container logs captured before a stop or reset removes them, so a stopped instance can
# still be read. Same volume as the git cache: state Fadebox owns and can lose.
FADEBOX_LOGS_CAPTURE_DIR: /data/logs
# The address users open Fadebox on, e.g. https://fadebox.example.com. Gating an
# instance needs it, and so does a managed ingress stack that cannot reach Fadebox on
# host.docker.internal:8080: a remote (tcp://) runtime, FADEBOX_PORT other than 8080, a
# proxy in front with no port published. Never localhost — inside Traefik's container that
# is Traefik itself — so leave it unset for a trial on this machine.
FADEBOX_PUBLIC_URL: ${FADEBOX_PUBLIC_URL:-}
volumes:
- ${DOCKER_SOCK:-/var/run/docker.sock}:/var/run/docker.sock
- fadebox-git:/data
ports:
# A managed ingress stack on this host polls fadebox on host.docker.internal:8080 unless
# FADEBOX_PUBLIC_URL says otherwise, so with FADEBOX_PORT other than 8080 set
# FADEBOX_PUBLIC_URL too, or fadebox never hears the poll and after a minute the stack's
# status reads it as STALE.
- "${FADEBOX_PORT:-8080}:8080"
# Healthy once Fadebox serves and reaches its database; `docker compose up --wait` waits for
# it. Docker only reports an unhealthy container — it restarts nothing.
healthcheck:
test: [ "CMD", "curl", "-fsS", "-o", "/dev/null", "http://localhost:8080/q/health/ready" ]
interval: 10s
timeout: 5s
retries: 3
start_period: 60s

volumes:
fadebox-db:
fadebox-git:

The image tag 1.0 is the release line: it moves to each 1.0.x patch as it ships and never to the next minor, so docker compose pull picks up fixes without a version jump. Every release also carries its exact tag (1.0.0) for an installation that wants nothing to move on its own; latest follows the newest release of any line, which is the one to avoid in a compose file that is meant to stay put.

2. Create .env​

Nothing host-specific is baked into the compose file, so it reads these from .env next to it (compose fails fast with a named variable if one is missing):

{
echo "DOCKER_GID=$(stat -c '%g' /var/run/docker.sock)" # macOS: stat -f '%g'
echo "FADEBOX_SESSION_KEY=$(openssl rand -base64 32)"
echo "FADEBOX_ENCRYPTION_KEY=$(openssl rand -base64 32)"
echo "DB_PASSWORD=$(openssl rand -base64 24)"
} >> .env

DOCKER_GID is the group that owns the Docker socket — docker on most Linux hosts, 0 on Docker Desktop, your own gid under rootless Docker (which also needs DOCKER_SOCK=/run/user/$UID/docker.sock). The app container keeps its own uid and just joins that group; without it, every deploy fails with permission denied on /var/run/docker.sock.

security_opt: label=disable is the other half of reaching the socket, on one class of host. Where SELinux is enforcing and the daemon runs with its SELinux integration on (docker info lists name=selinux under Security Options — the default on Fedora and RHEL), a container is confined to files labelled for containers, and the daemon socket is not one of them: the same permission denied follows whatever group the container is in. The option takes this one container out of that confinement, which for a container that manages the daemon anyway changes little. On a host without SELinux it does nothing. Fadebox sets the same option on the socket proxy of the managed ingress stack since 1.3.0, for the same reason.

FADEBOX_ENCRYPTION_KEY encrypts stored credentials (registry passwords, git tokens, mTLS client keys, OIDC client secrets) at rest, so a database dump or backup does not leak them. Keep it stable and include it in your backups: unlike FADEBOX_SESSION_KEY (rotating that one just signs everyone out), changing this key makes every stored credential unreadable until re-entered.

FADEBOX_PUBLIC_URL is optional, and a trial on this machine leaves it out. Anywhere else, add the address users open Fadebox on:

echo "FADEBOX_PUBLIC_URL=https://fadebox.example.com" >> .env

Requiring a sign-in on an instance needs it: the sign-in sends the visitor's browser there. So does a managed ingress stack that cannot reach Fadebox on host.docker.internal:8080: one on a remote (tcp://) runtime, Fadebox published on a port other than 8080, or a reverse proxy in front with no port published. It must never be localhost: inside Traefik's container that address is Traefik itself, so installing a stack is refused.

3. Start it​

docker compose up -d --wait

--wait returns once the app's healthcheck passes. It probes /q/health/ready, which needs no sign-in and fails while the database is unreachable — the same path a load balancer or an uptime monitor can watch, and docker compose ps shows the result as (healthy).

4. Sign in​

Open http://localhost:8080. The initial admin user's generated password is printed once in the log:

docker compose logs app | grep "generated password"

Sign in with it. Fadebox then requires a new password before the account can do anything else — a password somebody (or something) else chose is treated as temporary, so the first screen after this sign-in is the password form and every other page answers 403 until you are through it. The same applies to any account an admin creates or resets. See Passwords.

warning

Fadebox's app container mounts /var/run/docker.sock — managing a Docker daemon is effectively root on that host, so treat Fadebox admin access accordingly.

warning

This publishes Fadebox on plain HTTP on port 8080, which is fine on a laptop and not fine anywhere else: sign-in posts a password, and the session cookie is only marked Secure when the request arrives as https. For anything beyond a local trial, put a TLS-terminating reverse proxy in front and have it send X-Forwarded-Proto — see Reverse proxy.

Which version you are running​

The version sits in the sidebar footer, under your account, on every page — hover it for the date that build was made, and CLI beside it downloads the command-line client this server was released with. Administration → License shows the same two facts as a row, directly above the license's update horizon, which is checked against that build date; see Licensing.

The CLI reports its own build the usual way:

fadebox --version

Quote the server's version in any support conversation — it is the first thing that decides whether a behaviour is a bug or a release you have not upgraded to yet.

Next​

Deploy your first environment — ingress domain, a template from the catalog, and a running URL.

Optional, whenever you need them:

  • Remote runtimes to keep this VM small and deploy environments onto a powerful Docker host instead.
  • Single sign-on against your identity provider, configured in the UI.
  • Users, groups and roles for anything beyond the bootstrap admin.
  • Reverse proxy if you terminate TLS in front of Fadebox — the headers it needs from your proxy, and the one setting that stops them being spoofed.
  • Configuration reference for the environment variables the app understands.
  • Container logs — Docker keeps every line a container ever wrote until the daemon is told otherwise; two keys in daemon.json cap it.
  • Upgrading when the next release ships — what to check first, and the release notes that say what changed.