Install

Docker image

Run ImgYoung from the multi-arch image on GHCR - the run command, image tags, the data volume, environment variables and settings behind a reverse proxy.

Running it

Terminal
docker run -d --name imgyoung --restart unless-stopped \
  -p 8721:8721 \
  -v imgyoung-data:/data \
  ghcr.io/x-haose/imgyoung:latest

Open http://<host>:8721/ and sign in with admin / imgyoung123; the first sign-in asks you to change the password right away.

FlagPurpose
-d --name imgyoungRun in the background as a container named imgyoung, which later commands refer to
--restart unless-stoppedStart the process again after it exits; see "Restart policy" below
-p 8721:8721Publish the service port. If a reverse proxy on the same host is the only entry point, use -p 127.0.0.1:8721:8721 so 8721 is not exposed directly
-v imgyoung-data:/dataA named volume holding all state

The image is published for linux/amd64 and linux/arm64; Docker picks the right one for the host. To see the version inside an image:

Terminal
docker run --rm ghcr.io/x-haose/imgyoung:latest -version

Image tags

The image is ghcr.io/x-haose/imgyoung. Tags have no v prefix:

TagPoints toGood for
1.2.3One specific version; never changes after releaseProduction, where you decide exactly when to upgrade
1.2The latest stable release in the 1.2 series; moves when a patch release shipsGetting fixes automatically without crossing a minor version
latestThe latest stable releaseTrying it out and personal instances

Pre-releases get only their exact version tag and never move the major.minor tags or latest, so instances pinned to those never pull a pre-release.

The data volume

The data directory inside the container is /data (the image sets IMGYOUNG_DATA_DIR=/data). It contains:

PathContents
imgyoung.dbThe metadata database (SQLite in WAL mode, so imgyoung.db-wal and imgyoung.db-shm sit next to it while running): users, image records, channel configuration (credentials encrypted with the master key), runtime settings and the job queue
master.keyThe master key, generated with mode 0600 when a brand-new instance first starts without IMGYOUNG_MASTER_KEY. When IMGYOUNG_MASTER_KEY is set, this file is neither created nor read
blobs/Objects of the built-in local channel. While the local channel is healthy, metadata snapshots are also written here, under _imgyoung/backup/
variants/The cache of variants produced by on-demand processing, capped at 10240 MB by default and evicted least-recently-used first

During a restore from snapshot you may also see the staged file imgyoung.db.restore, and afterwards the previous database kept as imgyoung.db.pre-restore-<time>.

Back up master.key separately

Channel credentials and the data on encrypted channels can only be decrypted with the master key. It is not included in metadata snapshots or data exports, and once it is lost that data cannot be recovered. The database records a fingerprint of the master key, so if the key is missing or wrong the service refuses to start and says why, instead of carrying on with a new key. Copy it somewhere safe right after the first start:

docker cp imgyoung:/data/master.key ./imgyoung-master.key

Using a host directory instead

To keep the data in a directory on the host, first give it to the user the image runs as (uid 65532):

Terminal
sudo mkdir -p /srv/imgyoung
sudo chown 65532:65532 /srv/imgyoung
docker run -d --name imgyoung --restart unless-stopped \
  -p 8721:8721 \
  -v /srv/imgyoung:/data \
  ghcr.io/x-haose/imgyoung:latest

A named volume inherits the ownership of /data from the image the first time it is mounted; a host directory does not. With the wrong owner the process cannot create the database and fails at startup.

Environment variables

These are all bootstrap settings. Pass them with -e; changes take effect only after the container is recreated. See Configuration for the full reference.

A variable that is not set takes its default. Setting it to an empty string (or only whitespace) explicitly empties it instead of falling back to the default; the "When empty" column says what that means:

VariableDefaultWhen emptyDescription
IMGYOUNG_DATA_DIR/data (set by the image)Startup fails, naming the variableLeave it alone
IMGYOUNG_LISTEN:8721Startup fails, naming the variableListening address inside the container; rarely needs changing
IMGYOUNG_MASTER_KEYUses /data/master.keySame as not setA 64-character hex master key, e.g. from openssl rand -hex 32. When switching an existing instance to it, use the exact value from its current master.key; a wrong value makes startup fail
IMGYOUNG_TRUSTED_PROXIES127.0.0.0/8,::1/128Forwarding headers are never trustedReverse proxy networks whose forwarding headers are trusted; see below
IMGYOUNG_METRICS_ALLOWLoopback and private ranges/metrics is open to no oneNetworks allowed to scrape /metrics

Referencing host variables in Compose

With a reference such as IMGYOUNG_TRUSTED_PROXIES: ${TRUSTED_PROXIES} in compose.yaml, if the host does not set that variable, Compose only prints a warning and substitutes an empty string, which is the "When empty" case above: IMGYOUNG_LISTEN and IMGYOUNG_DATA_DIR make startup fail, and IMGYOUNG_TRUSTED_PROXIES ends up trusting no forwarding headers. An empty IMGYOUNG_MASTER_KEY falls back to reading /data/master.key; an instance whose master key comes only from that variable, with no master.key in the data directory, refuses to start and reports that the master key is missing, without generating a new one. When the host must provide a variable, write ${TRUSTED_PROXIES:?TRUSTED_PROXIES must be set}, which refuses to start when it is missing or empty; if you want the default, leave the entry out.

Running as nonroot

The image is based on gcr.io/distroless/static-debian12:nonroot and runs as the nonroot user (uid 65532). It contains no shell and no package manager, so docker exec -it imgyoung sh does not work. When you need to work on the data directly (for example editing the database to reset a forgotten password), stop the container and mount the same volume into another container that has the tools.

Behind a reverse proxy

ImgYoung trusts X-Forwarded-For, X-Forwarded-Proto and X-Forwarded-Host only when the connecting peer is inside IMGYOUNG_TRUSTED_PROXIES. The default trusts loopback only, but inside a container the peer is almost never loopback:

  • The proxy runs in another container on the same Docker network: the peer is the proxy container's address.
  • The proxy runs on the host and reaches the container through a port published with -p: the peer is usually the Docker bridge gateway (such as 172.17.0.1), not 127.0.0.1.

In both cases, mark the proxy's network as trusted:

Terminal
docker run -d --name imgyoung --restart unless-stopped \
  -p 127.0.0.1:8721:8721 \
  -v imgyoung-data:/data \
  -e IMGYOUNG_TRUSTED_PROXIES=172.16.0.0/12 \
  ghcr.io/x-haose/imgyoung:latest

172.16.0.0/12 covers most networks Docker allocates by default; check the actual subnet with docker network inspect <network>. If you leave it unset:

  • Login rate limiting is keyed on the proxy's address, so every visitor shares one allowance (a burst of 5, then 1 more every 12 seconds): when someone keeps mistyping a password, everyone else is temporarily locked out too.
  • Without base_url, links returned by uploads use http:// even though the site is served over HTTPS, and the session cookie is not marked Secure.
  • The /metrics allowlist also sees the proxy's (private) address, so anyone going through the proxy can scrape metrics unless the proxy blocks /metrics. The Caddy and Nginx samples under deploy/ in the repository already block it.

For the proxy itself, see Reverse proxy; for running it together with Caddy, see Docker Compose.

Restart policy

"Restore" under "Settings → Backups & export" in the console first stages the snapshot in the data directory, then makes the service exit cleanly (exit code 0); the snapshot is swapped in before the database is opened on the next start. Therefore:

  • Use unless-stopped or always. on-failure does not restart a container that exited cleanly, and the default no never restarts it.
  • Without a suitable policy, the container stays exited after a restore, and the snapshot is swapped in only when you run docker start imgyoung yourself.

The restart policy also brings ImgYoung back after the host or the Docker daemon restarts.

Upgrading

Pull the new image and recreate the container with the same flags. All data lives in the volume and is untouched; the database schema is migrated automatically when the new version starts:

Terminal
docker pull ghcr.io/x-haose/imgyoung:latest
docker stop imgyoung && docker rm imgyoung
docker run -d --name imgyoung --restart unless-stopped \
  -p 8721:8721 \
  -v imgyoung-data:/data \
  ghcr.io/x-haose/imgyoung:latest

docker stop waits 10 seconds by default before killing the container, which is enough for ImgYoung: on the stop signal it stops accepting new requests and waits up to 6 seconds for in-flight ones, stops background work within 2 more seconds, then closes the database and exits.

Downgrading is not supported: if you switch back to an older tag, the older version refuses to start on a database migrated by a newer one. Rolling back takes the older image plus a snapshot taken before the upgrade.

Before upgrading, consider clicking "Back up now" under "Settings → Backups & export". See Upgrading for the rollback steps and more.

On this page