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
docker run -d --name imgyoung --restart unless-stopped \
-p 8721:8721 \
-v imgyoung-data:/data \
ghcr.io/x-haose/imgyoung:latestOpen http://<host>:8721/ and sign in with admin / imgyoung123; the first sign-in asks you to change the password right away.
| Flag | Purpose |
|---|---|
-d --name imgyoung | Run in the background as a container named imgyoung, which later commands refer to |
--restart unless-stopped | Start the process again after it exits; see "Restart policy" below |
-p 8721:8721 | Publish 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:/data | A 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:
docker run --rm ghcr.io/x-haose/imgyoung:latest -versionImage tags
The image is ghcr.io/x-haose/imgyoung. Tags have no v prefix:
| Tag | Points to | Good for |
|---|---|---|
1.2.3 | One specific version; never changes after release | Production, where you decide exactly when to upgrade |
1.2 | The latest stable release in the 1.2 series; moves when a patch release ships | Getting fixes automatically without crossing a minor version |
latest | The latest stable release | Trying 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:
| Path | Contents |
|---|---|
imgyoung.db | The 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.key | The 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.keyUsing 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):
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:latestA 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:
| Variable | Default | When empty | Description |
|---|---|---|---|
IMGYOUNG_DATA_DIR | /data (set by the image) | Startup fails, naming the variable | Leave it alone |
IMGYOUNG_LISTEN | :8721 | Startup fails, naming the variable | Listening address inside the container; rarely needs changing |
IMGYOUNG_MASTER_KEY | Uses /data/master.key | Same as not set | A 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_PROXIES | 127.0.0.0/8,::1/128 | Forwarding headers are never trusted | Reverse proxy networks whose forwarding headers are trusted; see below |
IMGYOUNG_METRICS_ALLOW | Loopback and private ranges | /metrics is open to no one | Networks 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 as172.17.0.1), not127.0.0.1.
In both cases, mark the proxy's network as trusted:
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:latest172.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 usehttp://even though the site is served over HTTPS, and the session cookie is not markedSecure. - The
/metricsallowlist 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 underdeploy/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-stoppedoralways.on-failuredoes not restart a container that exited cleanly, and the defaultnonever 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 imgyoungyourself.
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:
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:latestdocker 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.