Homelab Academy / Modules
Guided practice + break/fix + lab gate.
Module: Requests & Media Servers
Activity type: Lab (Practice)
Lab risk: low
Objective: Describe the roles of Jellyfin configuration, cache, metadata, and media storage
All directories and files explicitly created by the lab are located under /opt/lab-classroom/class58/. Docker will manage the runtime container and its normal engine-managed image data, but no host configuration file is edited.
1
Create the isolated lab directory structure
install -d -m 0750 /opt/lab-classroom/class58/config /opt/lab-classroom/class58/cache /opt/lab-classroom/class58/media/Movies /opt/lab-classroom/class58/media/Shows /opt/lab-classroom/class58/media/Music /opt/lab-classroom/class58/media/Home-Videos
Run the command as an account that is permitted to create the assigned classroom path. Do not redirect any lab output to another directory.
2
Record the current account identity for the container
printf 'JELLYFIN_UID=%s\nJELLYFIN_GID=%s\n' "$(id -u)" "$(id -g)" > /opt/lab-classroom/class58/.env
Running the service with the preparing account's numeric identity allows it to write configuration and cache data without granting access as the container root user.
3
Create the Docker Compose definition
cat > /opt/lab-classroom/class58/compose.yaml <<'YAML'
services:
jellyfin:
image: jellyfin/jellyfin:10.10.7
container_name: class58-jellyfin
user: "${JELLYFIN_UID}:${JELLYFIN_GID}"
ports:
- "127.0.0.1:8096:8096"
environment:
JELLYFIN_PublishedServerUrl: "http://127.0.0.1:8096"
volumes:
- ./config:/config
- ./cache:/cache
- ./media:/media:ro
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
restart: unless-stopped
YAML
Relative bind-mount sources are resolved from the directory containing compose.yaml. The media mount is deliberately read-only.
4
Validate the rendered Compose configuration
docker compose --env-file /opt/lab-classroom/class58/.env -f /opt/lab-classroom/class58/compose.yaml config
Review the rendered port, user, image, and mount paths before starting the service. Correct validation errors rather than bypassing them.
5
Start Jellyfin
docker compose --env-file /opt/lab-classroom/class58/.env -f /opt/lab-classroom/class58/compose.yaml up -d
The first start may pull the declared image. The web service remains limited to the host itself.
6
Inspect startup status
docker compose --env-file /opt/lab-classroom/class58/.env -f /opt/lab-classroom/class58/compose.yaml ps
docker compose --env-file /opt/lab-classroom/class58/.env -f /opt/lab-classroom/class58/compose.yaml logs --tail=100 jellyfin
The container should remain running. Initial informational messages about database creation and startup are expected.
7
Complete the setup wizard
Open http://127.0.0.1:8096 in a browser on the Jellyfin host. Select a language, create a unique administrator name and strong password, and add separate libraries using /media/Movies, /media/Shows, /media/Music, and /media/Home-Videos. Choose the matching content type for each path. Do not use /opt/lab-classroom/class58/media in the wizard because that host path does not exist inside the container.
8
Optionally add authorized test media
Copy only media you are authorized to use into the appropriate directory under /opt/lab-classroom/class58/media/. Use the naming patterns taught in this lesson. The destination must remain inside the class58 lab tree.
9
Run a library scan and inspect identification
From the Jellyfin dashboard, scan all libraries. If test media was added, verify the title, release year, season, episode, and artwork. Correct source naming before manually overriding large numbers of mismatches.
10
Confirm persistent files were created
find /opt/lab-classroom/class58/config -maxdepth 2 -type f -print | head -n 20
find /opt/lab-classroom/class58/media -maxdepth 3 -type d -print
The first command should display Jellyfin-created configuration or database files. The second should display the planned library directory hierarchy.
docker compose --env-file /opt/lab-classroom/class58/.env -f /opt/lab-classroom/class58/compose.yaml config and confirm that the rendered configuration contains only the intended class58 bind mounts.docker compose --env-file /opt/lab-classroom/class58/.env -f /opt/lab-classroom/class58/compose.yaml ps and confirm that class58-jellyfin is running.:ro.docker compose --env-file /opt/lab-classroom/class58/.env -f /opt/lab-classroom/class58/compose.yaml restart jellyfin, then confirm that the configured account and libraries remain present.| Symptom | Likely cause | Fix |
|---|---|---|
| Docker Compose reports that JELLYFIN_UID or JELLYFIN_GID is unset. | The .env file was not created, was written to another directory, or was not supplied with the command. | Inspect /opt/lab-classroom/class58/.env, regenerate it using the lesson command, and include the documented --env-file argument. |
| The container exits immediately or repeatedly restarts. | Jellyfin cannot write to the configuration or cache bind mount, or the Compose file contains an invalid customization. | Read the container logs, confirm that config and cache are owned and writable by the UID and GID recorded in .env, and compare the rendered Compose configuration with the lesson definition. |
| The browser cannot connect to 127.0.0.1:8096. | The container is not running, another process already uses port 8096, or the browser is not running on the Jellyfin host. | Check Compose status and logs. Confirm that the browser is local to the host. If port 8096 is occupied, choose an unused loopback host port in compose.yaml while leaving the container-side port as 8096. |
| Jellyfin reports that a library path does not exist. | A host path such as /opt/lab-classroom/class58/media/Movies was entered in the wizard instead of the container path. | Edit the library and use /media/Movies, /media/Shows, /media/Music, or /media/Home-Videos as appropriate. |
| Media directories exist, but titles are not discovered. | The directory is empty, files use unsupported formats, the service identity lacks read and traversal access, or the scan has not run. | Confirm that authorized files exist beneath the expected path, verify that the recorded UID and GID can read them, and initiate a library scan. |
| A movie or episode is matched to the wrong title. | The filename is ambiguous, the release year is absent, or season and episode notation is inconsistent. | Rename the source within the class58 media tree using the documented movie or series pattern, scan the library again, and use manual identification only when naming cannot disambiguate the item. |
| Bind mounts are denied on a host with mandatory access controls enabled. | The container runtime is not permitted to access the current labels on the class58 directories. | Follow the host platform's documented container-labeling procedure. If the platform supports the Compose Z bind option, apply it only to the three class58 bind mounts and revalidate the rendered configuration. |
| Playback starts but consumes substantial CPU or fails for a particular client. | The client cannot direct play the stored streams and Jellyfin is attempting software transcoding. | Inspect the Jellyfin playback information to determine whether direct play, remuxing, or transcoding is occurring. Treat hardware acceleration as a separate future change rather than adding unverified host devices during this lab. |
| Jellyfin cannot save artwork beside the media files. | The media bind mount is intentionally read-only. | Keep downloaded metadata in Jellyfin's configuration storage for this lab. Do not make source media writable merely to store artwork beside it. |
The web port is bound to 127.0.0.1 instead of all host interfaces.
The container runs with the preparing user's numeric UID and GID rather than the container root identity.
All Linux capabilities are dropped and no-new-privileges is enabled.
Source media is mounted read-only.
Only configuration and cache paths are writable by Jellyfin.
A unique administrator account and strong password are required during setup.
The deployment does not expose host hardware devices or unrelated host directories.
Production access should use an independently reviewed encrypted ingress design rather than directly publishing this classroom service.
Back up the configuration directory before upgrades or significant library changes. Source media requires its own backup strategy because the Jellyfin database and metadata do not replace the original files. Cache is generally lower priority because it can be regenerated.
Media names, watch history, user names, images, and metadata can reveal personal interests or household information. Restrict administrative access and avoid uploading private home-video information to unnecessary external metadata services.
Do not reuse an infrastructure administrator password. Create individual non-administrator users for routine playback and reserve the Jellyfin administrator account for management.
Do not add host root directories, personal home directories, device nodes, or container-engine control interfaces as Jellyfin mounts.
Stop and remove the classroom container while retaining all bind-mounted configuration, cache, and media files under /opt/lab-classroom/class58/.
docker compose --env-file /opt/lab-classroom/class58/.env -f /opt/lab-classroom/class58/compose.yaml down
Run the documented Compose up command again. Jellyfin should recover its prior account and library configuration from the preserved config directory.
A full reset requires first stopping the container and then deliberately removing only selected contents beneath /opt/lab-classroom/class58/. Preserve authorized source media and copy any required configuration backup before resetting. No deletion command is provided because reset requirements differ and accidental selection of the media directory would destroy source files.