# Public installation and release guide Magent runs as one non-root Linux container containing the Python API and the Next.js frontend. Connect your own media services in the setup wizard; no pre-existing Magent account or database is required. Optional integrations may be skipped. Media files remain in your existing media services. The lightweight Dockerfile builds the frontend separately and copies only its standalone runtime, static assets and public files into the final image. Build tools, the full development dependency tree and package-manager caches are not runtime requirements. The image still needs both Python and Node to serve the API and frontend; it is not a static website. Both build and runtime stages use Alpine Linux, so native dependencies are built for its musl runtime rather than copied from an incompatible glibc image. The non-root UID/GID and `/app/data` layout are unchanged. Existing installations do not need new databases, replacement keys or new data volumes for this change. Magent's own source code is licensed under the [MIT License](../LICENSE). Bundled dependencies retain their own licenses; Magent's license does not relicense third-party software or grant rights to third-party branding. ## Fresh installation For the simplest **Portainer** installation, use the image-only root [compose.yml](../compose.yml) and follow the [Portainer guide](PORTAINER.md). That fresh-install path pulls `rephl3xnz/magent:latest` from Docker Hub with no environment inputs, automatically persists its private keys and requires no Dockerfile or `.env`. Confirm the browser-facing application URL in the token-authorized first-administrator wizard; matching CORS and cookie security follow the saved URL automatically. Managed SQLite stays at `/app/data/magent.db`, and API documentation stays disabled. The managed-install image is published on Docker Hub. Repository changes alone do not update an existing deployment: explicitly pull the image when upgrading, retaining the same persistent volume. `latest` is mutable, so record the resolved digest before an upgrade; older cached images may lack this setup workflow. The Compose runtime-security block stays enabled and must be left unchanged. See the [complete environment reference](ENVIRONMENT.md) for all supported variables and the difference between managed, manual and tooling controls. ### Advanced/manual secrets The procedure below remains supported for operators who manage their own deployment keys. It uses `docker-compose.hub.yml`, not the zero-input root `compose.yml`; its required environment inputs are specific to this manual path. Prerequisites: Docker Engine or Docker Desktop running Linux containers, Docker Compose v2, and an image release published for your host's architecture. Use the Compose and environment examples from the **same release** as the image. 1. Put `docker-compose.hub.yml` and a copy of `.env.example` named `.env` in a private deployment directory. Keep the directory and Compose project name stable: the project owns the persistent named volume. On Linux restrict `.env` to the deployment account, for example `chmod 600 .env`; on Windows restrict its file permissions. Do not commit it, paste it into support reports or expose it through a web server. 2. Set `MAGENT_IMAGE` to the release's published immutable tag, for example `rephl3xnz/magent:prod-`, or its published `rephl3xnz/magent@sha256:`. Replace the angle-bracket placeholders. There is deliberately no implicit `latest` fallback. Confirm the selected release contains the setup/backup features before following this guide. 3. Generate three **independent** values. Run these on a trusted machine; the commands print secrets, so do not share their output or put it in CI logs: ```bash # JWT_SECRET python -c "import secrets; print(secrets.token_urlsafe(48))" # SETUP_TOKEN (not the JWT secret) python -c "import secrets; print(secrets.token_urlsafe(48))" # SETTINGS_ENCRYPTION_KEY (Fernet-compatible, using only Python's standard library) python -c "import base64, secrets; print(base64.urlsafe_b64encode(secrets.token_bytes(32)).decode())" ``` Put each value in its matching `.env` field. Leave `ADMIN_PASSWORD` blank to create the administrator through the wizard. There is no shared default administrator password. Save the signing and encryption keys in a separate secure backup; generating replacement keys is **not** an upgrade step. 4. For a local trial, keep `MAGENT_BIND_ADDRESS=127.0.0.1`, `MAGENT_HTTP_PORT=3000`, the localhost URLs and `AUTH_COOKIE_SECURE=false`. For internet access, configure HTTPS as described below **before** inviting users. If changing the local port, update both browser-facing URLs too. 5. Validate without printing expanded secrets, pull and start: ```bash docker compose -p magent -f docker-compose.hub.yml config --quiet docker compose -p magent -f docker-compose.hub.yml pull magent docker compose -p magent -f docker-compose.hub.yml up -d --no-build magent docker compose -p magent -f docker-compose.hub.yml ps ``` 6. Open `http://localhost:3000` on the Docker host (or your configured HTTPS address). The fresh database opens `/setup`. Enter the setup token, create your administrator, connect the apps you use, review preferences and finish. App URLs must be reachable **from the container**; `localhost` means Magent itself, not another container or the Docker host. Use your internal DNS, reachable LAN addresses or service names on an explicitly shared Docker network. 7. Remove `SETUP_TOKEN` from `.env` and recreate only Magent using the same `up -d --no-build magent` command. Setup progress and accounts remain in the volume. Make and test a backup before relying on the installation. The template creates a project-scoped named volume at `/app/data`. The image's data directory is prepared for UID/GID `1000:1000`, avoiding the fresh root-owned bind-directory problem. The container runs with a read-only root filesystem, dropped capabilities and private temporary writable areas. The backend's port 8000 is not published: browser API calls use `/api` on the frontend port. Do not run `docker compose down --volumes` unless you intend to erase the data. ## HTTPS, public URLs and reverse proxies For the **managed Portainer path**, visit the intended HTTPS address and confirm it in first-administrator setup. Later application URL changes belong in admin configuration. CORS and Secure cookies follow the saved URL; no environment values are required. Setup does not provision DNS, certificates or a proxy. For the **advanced/manual path**, set these deployment values to **your own** origin, without a path or trailing slash: ```dotenv CORS_ALLOW_ORIGIN=https://magent.example.com MAGENT_APPLICATION_URL=https://magent.example.com AUTH_COOKIE_SECURE=true AUTH_COOKIE_SAMESITE=strict ``` The supplied image already routes browser `/api/*` calls to its internal API. Do not expose port 8000, set a public API hostname or override the internal backend URL for this combined-image deployment. Runtime environment changes do not rebuild the frontend's compiled routing configuration. With a reverse proxy running directly on the **same host**, use a loopback bind and route the whole hostname, including `/api`, to `127.0.0.1:3000` (or your chosen host port). The manual template defaults to loopback; in the root Portainer template deliberately change its port mapping to `127.0.0.1:3000:3000` for this topology. For example, a host-running Caddy instance can use: ```caddyfile magent.example.com { reverse_proxy 127.0.0.1:3000 } ``` For a containerized proxy, loopback inside that proxy is not the Docker host. Attach the proxy and Magent to an intentionally shared Docker network and proxy to `magent:3000`, or configure a reachable host address explicitly. If the proxy is on another machine, bind the frontend host port to the Docker host's private interface address and restrict access with a firewall to the trusted proxy. In the root Portainer template edit the port mapping directly; the manual Hub template instead supports `MAGENT_BIND_ADDRESS`. Avoid exposing all interfaces merely to work around routing. Provide valid TLS, suitable DNS and upload limits of at least 34 MiB for backup restore. Do not cache login, setup, authenticated pages or API responses at the proxy/CDN. Keep the exact public origin configured: the request-origin guard does not trust arbitrary `Host` or forwarded headers. Keep `API_DOCS_ENABLED=false` for public service. TLS terminates at your proxy, not inside this image. ## Existing installations and upgrades **Do not replace an existing deployment with the fresh named-volume template.** Keep its existing project name, mount, database path, environment, signing key and encryption key. Switching from `./data:/app/data` to a new named volume makes an existing installation look empty; it does not migrate data. Never complete fresh setup to fix a missing mount. For existing Linux bind mounts, confirm the exact intended data directory and its files are writable by UID/GID `1000:1000`. Back up first and correct only that application directory if needed; do not recursively change an entire shared stack, host directory or filesystem. Do not work around permissions by running Magent as root. Custom UID/GID images require matching ownership. Before upgrading: 1. Save the old image tag **and digest**, Compose definition and protected environment/key backup. Create a consistent data backup and verify recovery. 2. Read the target release's migration notes and select its immutable image. Pull it before interrupting service. Validate the actual saved deployment definition, including a Portainer stack's saved environment if used; editing a separate host Compose file does not update Portainer's copy. 3. Recreate **only Magent**, retaining the original data mount and secrets. For this template use `docker compose -p magent -f docker-compose.hub.yml up -d --no-deps --no-build magent`. Use your actual project/file for other stacks. 4. Verify health, real local-account login, any enabled Jellyfin login, requests, app connections and invite behaviour. Do not trigger imports, notifications or destructive repair actions merely as a smoke test. The zero-input Portainer template deliberately uses `latest`. On that path, review the new release and pull its updated image when updating the existing stack; restarting alone leaves the current image in use. Save the previous digest because `latest` can move. Operators preferring controlled releases may replace its literal `image:` value with a published immutable tag/digest without changing the volume or generated keys. For rollback, select the recorded image and recreate only Magent. Database migrations may prevent older versions from reading newer data: follow the release's compatibility notes and restore the matching backup if required. Restoring old data discards changes since that backup. Never restore a whole shared stack file over unrelated service changes. ## Backups and recovery For managed Portainer installations, retain `bootstrap-secrets.json` alongside the database in consistent offline volume backups; the generated encryption key is essential to recovering a raw database copy. It is deliberately excluded from portable encrypted application exports. See [Portainer persistence](PORTAINER.md#persistence-backups-and-upgrades). See [installation and recovery](installation-and-recovery.md) for the encrypted admin backup/export flow and its size limits. It supports configuration, database and optional artwork cache. The backup passphrase cannot be recovered. Keep it separately and test a restore to a disposable instance of the same version. For an offline volume backup, stop **only Magent** and snapshot/copy its complete data volume, including SQLite journal/WAL sidecars. Start it again after the consistent copy completes. Keep `.env` and the original encryption/signing keys separately protected: encrypted settings in a raw database copy cannot be recovered without the original encryption key (or original signing key for older installations that derived their encryption key from it). The portable encrypted application backup re-encrypts settings for its destination instead; these are different recovery procedures. Managed portable restore keeps the destination's confirmed application URL as well as its signing/encryption keys. It does not replace the destination's trusted origin with the source backup's URL; review integration addresses and sign in using the destination URL after recovery. Never rotate keys, replace the database or delete volumes as a response to an unhealthy container. Inspect health/log errors without posting credentials. ## Public release gates The release branch is a source snapshot, not a published image. Build and verify the exact commit before publishing Docker Hub tags. This guide and the lightweight Dockerfile do not by themselves certify a release. Before publishing a new immutable tag or moving `latest`: - Build from the reviewed release commit with the root Dockerfile and its pinned base digests. Do not send `.env`, live data, local caches or development credentials in the build context. Record source revision, final image digest, compressed download size and unpacked image size separately. - Run backend and frontend checks, the production build and a **container** smoke test using the read-only Compose settings, a fresh named volume and non-root user. Confirm setup survives recreation, login works and the UI hydrates without CSP errors. Test backup/restore and an upgrade against a disposable copy, preserving its keys; never use live accounts/data for destructive tests. - Check same-origin POST protection: empty-form requests to both `/api/auth/login` and `/api/auth/jellyfin/login` with the configured origin must reach validation (422); an unrelated origin must be rejected (403). These are necessary checks, not substitutes for a successful authenticated login. - Inspect the final image for development packages, build caches, unexpected credentials, unnecessary privileged execution and known dependency/base-image vulnerabilities. Review dependency notices and licensing before redistribution. - Declare only architectures actually built **and smoke-tested**. A Linux/amd64 test is not an ARM64 test; an amd64 image running under emulation is not native ARM64 validation. Do not advertise a multi-architecture release until its manifest and each advertised platform have been verified. Docker Desktop runs the Linux image; this is not a native Windows-container or macOS build. - Publish migration/rollback notes, supported image tags/digests and a support and private security-reporting route. Include the MIT license and required dependency notices, and confirm rights to any bundled branding assets. Publish only with the release owner's approval; building locally does not publish or deploy an image.