# Unified container image, profiles, and migration

TautWeekly v0.23.0 publishes one multi-architecture OCI image for Docker
Desktop, generic Docker Compose, NAS, QNAP, Unraid, FreeBSD/Podman, and
compatible Linux-container systems:

    ghcr.io/sparkmoxie/tautweekly:0.23.0

The image contains the shared application and Manager payload for linux/amd64
and linux/arm64. The host selects a small, validated runtime profile; profiles
change host-facing defaults and reporting, not the private data format.

## Profile contract

| Profile | Intended host | Manager/network contract | Package identity |
|---|---|---|---|
| desktop | Docker Desktop, including Intel and Apple-silicon Macs | Mac Manager mode; loopback-first host publication; named-volume default; host.docker.internal guidance | container-desktop |
| server | Generic Compose, QNAP, NAS, and FreeBSD/Podman | NAS Manager mode; intentional trusted-LAN publication; bind-mount default; PUID/PGID ownership | container-compose, nas-docker, qnap-container-station, docker-compatible, or freebsd-podman |
| unraid | Maintained Unraid Community Applications template | NAS Manager mode; trusted-LAN publication; Unraid appdata and Apps-owned lifecycle | unraid |

Set TAUTWEEKLY_RUNTIME_PROFILE explicitly in new deployments. The container
refuses an unknown profile or an incompatible profile/package combination with
exit status 64 before the Manager or scheduler starts. Existing v0.22.0
definitions that omit the variable remain compatible: Mac package identities
infer desktop, Unraid infers unraid, and other supported container identities
infer server. This inference is a migration bridge, not the preferred new
configuration.

A later compatible container system can reuse one of these profiles with its
own package identity and host adapter. Adding a platform does not require a
second application payload or image repository.

## Preferred and fallback routes

The preferred container route is the published unified image plus a
release-checksummed Compose asset, the maintained Unraid template, or a
platform-native container tool that preserves the same contract. Windows
remains a native Manager installation. Native systemd Linux remains preferred
where a native service is wanted. FreeBSD retains its documented Podman beta
route.

The NAS and Mac archives remain supported host-adapter, recovery, and
local-build fallbacks. They are not required for a no-clone registry
installation. Do not replace a working native Windows or Linux installation
with a container merely to follow this consolidation.

## Image references and publication boundary

Use the full release tag for reviewed deployments:

    ghcr.io/sparkmoxie/tautweekly:0.23.0

For unattended automation, append the release manifest digest shown by the
release and registry:

    ghcr.io/sparkmoxie/tautweekly:0.23.0@sha256:<manifest-digest>

The digest is immutable. The full semantic-version tag is the readable stable
default. The minor, latest, and edge tags are mutable and are not recommended
CI/CD promotion references. Unraid may retain latest because its Apps workflow
is explicitly host-owned and digest-aware; administrators should review each
update before applying it.

The release workflow publishes the amd64/arm64 manifest, provenance, and SBOM
before it creates the GitHub release. The release job is gated on the image
job and on checksummed artifact validation, so a GitHub release cannot silently
advertise an image that its workflow failed to publish. Registry publication
and the GitHub release API are not transactional: if the final release call
fails, the semver image may briefly exist first. In that case the maintainer
reruns the failed release job for the same tag and commit; the tag is never
moved to different content.

## Before either migration

1. Record the running image reference, image ID, manifest digest, runtime
   profile, package kind, Compose/template revision, ports, networks, and every
   environment override.
2. Identify exactly what is mounted at /data. It may be a named volume or a
   host bind mount. Do not create a fresh empty mount for the replacement.
3. Back up /data privately while preserving ownership and modes. It contains
   credentials, Manager authentication, schedules, state, output, history,
   cache, and recipient-related information. Keep the backup out of the
   repository and public support reports.
4. For bind mounts, record PUID, PGID, and UMASK. Keep the same non-root numeric
   identity. For named volumes, record the exact volume name.
5. Record the current Manager URL and confirm that the administrator password
   works. The migration preserves the password hash and pairing state.
6. Disable or avoid starting a new delivery during the recreate. A normal stop
   allows up to 30 minutes for an active operation to drain, but a host that
   enforces a shorter hard stop can interrupt it.
7. Pull the new image before removing the healthy old container. A failed or
   interrupted pull leaves the running container and /data unchanged.

Never use docker compose down -v, docker volume rm, or an Unraid appdata delete
during update, migration, rollback, reinstall, or access recovery.

## Migrate the v0.22.0 Mac-specific image

This path starts from
ghcr.io/sparkmoxie/tautweekly-mac:0.22.0, whose release manifest digest was
sha256:982252b1140acf5ee2448668f14fb18683ea27801e218eb09704621232ae03fa.

1. Download TautWeekly-mac-compose.yaml and SHA256SUMS.txt from the v0.23.0
   release and verify the Compose checksum.
2. Compare the new file with the active definition. Preserve the existing
   tautweekly-data named volume or the exact existing bind mount, host port,
   private .env settings, allowed hosts, secure-cookie policy, PUID, PGID,
   UMASK, timezone, and any intentional network attachment.
3. Confirm these new values:

       image: ghcr.io/sparkmoxie/tautweekly:0.23.0
       TAUTWEEKLY_RUNTIME_PROFILE: desktop
       TAUTWEEKLY_PACKAGE_KIND: container-desktop

4. Pull and recreate without deleting the volume:

       docker compose pull tautweekly
       docker compose up -d --no-build --force-recreate tautweekly
       docker compose ps

5. Wait for healthy status. Open http://localhost:8787/, sign in with the
   existing Manager password, and confirm Settings > Updates reports the
   unified repository, desktop profile, current stable version, and host-owned
   Docker Desktop lifecycle.
6. Run Validate, save, and verify, inspect all six previews, and send only the
   controlled TestEmail before accepting the migration. The existing schedule,
   configuration, history, and pairing state should already be present.

For Mac-hosted Plex or Tautulli, continue to use host.docker.internal. Container
localhost still refers to TautWeekly. The desktop Compose default publishes the
Manager only on 127.0.0.1. Keep that fixed loopback mapping; use Tailscale
Funnel for optional public remote access.

To roll back, restore the prior Compose file or set the recorded old image
reference, keep the same /data volume, and recreate. Do not restore an older
data backup unless the release notes identify an incompatible data change or
the current data itself is damaged.

## Migrate the v0.22.0 NAS or generic image

This path starts from ghcr.io/sparkmoxie/tautweekly:0.22.0. The repository name
does not change; the explicit profile and semver-pinned release Compose asset
are the important changes.

1. Download TautWeekly-compose.yaml and SHA256SUMS.txt from the v0.23.0 release
   and verify the checksum, or refresh the verified NAS/QNAP host package.
2. Preserve the exact existing bind-mounted data directory or named volume,
   PUID, PGID, UMASK, timezone, port/bind settings, allowed hosts, secure-cookie
   policy, and Docker networks.
3. Confirm the new generic/QNAP definition uses:

       image: ghcr.io/sparkmoxie/tautweekly:0.23.0
       TAUTWEEKLY_RUNTIME_PROFILE: server
       TAUTWEEKLY_PACKAGE_KIND: container-compose

   A QNAP package may retain qnap-container-station and the NAS archive may
   retain nas-docker as package identity; both are compatible with server.
4. Pull, recreate with the original deployment tool, and wait for health:

       docker compose pull tautweekly
       docker compose up -d --no-build --force-recreate tautweekly
       docker compose ps

5. Open the existing trusted-LAN Manager URL, sign in with the existing
   password, and confirm Settings > Updates reports server plus the unified
   repository. Repeat Manager verification, six previews, and TestEmail.

Server and Unraid profiles intentionally retain trusted-LAN guidance. Container
localhost is not the NAS host. Use a shared private Docker network, a reachable
NAS/LAN address, or the platform's supported host gateway. Never public-forward
the Manager's plain HTTP port.

## Migrate or refresh Unraid

Update the saved Community Applications template before applying the image
update. Preserve the existing appdata path, WebUI port, PUID, PGID, UMASK,
timezone, allowed-host and secure-cookie settings. Confirm the hidden Runtime
profile is unraid and Package kind remains unraid. Apply the update from
Unraid Docker/Apps, wait for healthy status, then sign in and run the same
Manager acceptance sequence. Do not delete appdata and do not add privileged
mode, root identity, a Docker socket, or a TUN device.

## Named-volume and bind-mount permission checks

A named volume is owned from inside the container using the configured non-root
identity. Back it up with a trusted temporary container or host tool that
preserves numeric ownership and file modes.

For a bind mount, the host directory must be writable by PUID:PGID. Keep
UMASK=077 unless a documented integration requires another restrictive value.
The entrypoint may repair legacy root-owned entries within /data but refuses
PUID or PGID 0 and does not follow symlinks out of the data filesystem. If a
host ACL prevents repair, stop the container and correct that exact data
directory from the trusted host; do not grant broad root or privileged access.

## Interrupted pull, recreate, and recovery

- Interrupted pull: rerun the pull. Do not remove the healthy running
  container merely because a new manifest is incomplete.
- Interrupted recreate before replacement starts: rerun the same host-owned
  recreate against the same /data mount.
- New container unhealthy: inspect the production health result and recent
  sanitized logs, verify profile/package compatibility and permissions, then
  recreate with the recorded prior semver/digest.
- Manager asks for first-run pairing unexpectedly: stop. Verify that the
  original /data volume or bind mount is attached. Do not create a new password
  in an empty replacement volume.
- Forgotten password with the correct data attached: use access-recover,
  restart, retrieve a new one-time bootstrap token explicitly, and pair again.
  This removes only Manager access material; it preserves configuration,
  schedules, state, output, backups, and history.
- Interrupted delivery: inspect the sanitized operation and scheduler state
  after recovery before manually retrying. Do not assume a process exit means
  mail was or was not accepted.

## Old Mac image retirement and compatibility window

ghcr.io/sparkmoxie/tautweekly-mac:0.22.0 remains a documented rollback source
and is not deleted or retagged. No v0.23.0 or later release is published to that
repository, and it is no longer recommended for new installs.

TautWeekly-mac-compose.yaml remains a transitional release asset through the
v0.23.x and v0.24.x lines. It points to the unified image and selects desktop;
it is not a second image payload. It may be retired no earlier than v0.25.0,
and only with release-note notice. The canonical generic asset is
TautWeekly-compose.yaml. Existing archive/local-build Mac installations remain
a supported break-fix path during this window and retain their data-preserving
host updater and rollback behavior.

The Manager never receives a Docker socket, container-engine credentials, or a
host update helper. Settings reports the unified image, active profile,
migration state, stable release, semver/digest recommendation, and recovery
steps; the host administrator or platform UI always performs the update.
