At this level, colleagues depend on something you built. A container makes an application repeatable; it does not make the application persistent, reachable, secure, or recoverable. You own those boundaries and the evidence that they work.
2. "Just run this Compose file"
You receive a compose.yaml for a local AI service. The instructions say to run one command. Instead, the command returns a wall of text. On another attempt, two containers appear, but the local endpoint does not answer. After a cleanup, the downloaded model has vanished and must be fetched again.
None of these failures is mysterious once the objects are separated. An image is the packaged software. A container is one replaceable running copy. A volume holds data outside that copy. A network joins services. A published port creates a route from the host into one service. Compose declares how those objects fit together.
In this book you will run a small local AI stack with two services: Ollama provides inference, and a constrained probe checks Ollama through the private Compose network. Ollama is published only on the host's loopback address. Its model files live in a named volume. You will inspect the running objects, read logs, deliberately break a service address, stop and recover the service, restart it, delete its container, and prove the model remains.
The Lab framing extracts fields from a fictional literature note. The Company framing classifies a fictional supplier message. The infrastructure is identical. Only the synthetic prompt and the accountable reviewer change.
3. After this you can
- Distinguish an image, container, volume, network, and published port in a running stack.
- Run, inspect, diagnose, stop, restart, and remove named containers without acting on unrelated workloads.
- Keep model data in a named volume and prove it survives restart and container replacement.
- Operate a two-service local AI stack with either Docker Compose or Podman Compose.
- Recover from an expected service stop and record observable checks rather than claiming "it seems up."
4. Prerequisites
T11-L02· A local model your tools can call.- An approved non-production computer with either Docker Compose or Podman Compose. Do not use a shared, staging, production, or remote container connection.
- Permission to pull two reviewed images, create local containers and a named volume, and bind loopback port
11434. - One approved Ollama model identifier and enough disk and memory for it. The model installed directly on your host is not automatically available inside the container.
- A terminal, text editor, and 90 minutes for the independent exercise. Initial image and model downloads may require an approved network window.
Use only the synthetic prompts in this book. Do not put participant, patient, customer, supplier, credential, contract, unpublished, or confidential data into the stack merely because it runs locally. A container can still reach networks, mounts, logs, and administrators that you permit.
Choose one engine for the whole exercise. The worked commands use docker compose. If you use Podman, replace the leading docker with podman and first confirm that podman compose version reports an approved Compose provider. Do not alternate engines in the same project directory: their images, networks, volumes, and current connection may differ.
Windows
Docker Desktop commonly runs Linux containers through WSL 2. Podman Desktop commonly uses a Podman machine. Confirm the approved local backend; do not create, reset, resize, or replace a machine that belongs to another project.
docker context show
docker version
docker compose version
Get-NetTCPConnection -LocalPort 11434 -ErrorAction SilentlyContinue
For Podman, use podman system connection list, podman version, and podman compose version instead of the first three commands. Stop if the selected connection is remote or if port 11434 already has an owner you do not control. Run the remaining PowerShell examples with docker; Podman users substitute podman.
macOS
Docker Desktop and Podman both use a Linux virtual machine on macOS. A path, port, and process cross that VM boundary only through explicit runtime configuration. Confirm the local context or connection; do not broaden Desktop file sharing for this exercise.
docker context show
docker version
docker compose version
lsof -nP -iTCP:11434 -sTCP:LISTEN
For Podman, use podman system connection list, podman version, and podman compose version. An empty lsof result means no current listener was found. An existing listener is a reason to identify its owner, not to change the Compose port to 0.0.0.0.
Linux
Docker daemon access is privileged on a typical Linux installation; Docker documents that the docker group grants root-level privileges. Use the already-approved account and local context. Podman can run rootless, which reduces host privilege but does not make unsafe images, mounts, ports, or secrets safe.
docker context show
docker version
docker compose version
ss -lnt | grep ':11434 ' || true
For Podman, use podman system connection list, podman version, and podman compose version. Do not solve an access failure by changing socket permissions, adding yourself to an administrative group, or prefixing every command with sudo.
5. The idea in one page
Five command patterns cover most of the work
Use the project directory as a boundary. Read the rendered configuration before creating anything, then operate named services from that same directory.
| Pattern | Question it answers | Typical command |
|---|---|---|
| Up | Can Compose create and run the declared services? | docker compose up -d |
| List and inspect | What exists, what state is it in, and what did it receive? | docker compose ps --all; docker inspect NAME |
| Logs | What did the main process report during startup or failure? | docker compose logs --tail 100 SERVICE |
| Stop and start | Does the service recover through its normal lifecycle? | docker compose stop SERVICE; docker compose start SERVICE; docker compose restart SERVICE |
| Down | Can Compose remove this project's containers and network? | docker compose down |
down normally preserves declared named volumes; down --volumes deletes them. Never make --volumes habitual or run down from an unverified project directory.
An image is not a container
An image is a read-only package from a registry or Containerfile. A version tag can move; a digest identifies exact content. Review the source, pin the release, record the resolved digest, and avoid latest for a depended-on stack.
A container is one image instance plus runtime settings. Its writable layer is scratch space, not durable storage. Restart usually keeps that layer; replacement removes it. Replacement is therefore the stronger persistence test.
A volume is a separate lifecycle
A named volume is engine-managed storage mounted inside a container. Here, t11-l03-ollama-models mounts at Ollama's model path /root/.ollama and survives container recreation.
Persistence is not backup: a volume can be deleted, corrupted, or lost with its host. This exercise proves replacement survival only. Record its owner, deletion decision, and whether reviewed source data can be downloaded again.
A network is not a published port
Compose creates a private project network where the probe calls stable service name http://ollama:11434, not a changing container IP. That route does not expose Ollama to the host.
The separate mapping 127.0.0.1:11434:11434 publishes Ollama on host loopback. 0.0.0.0 would listen on all IPv4 interfaces. Do not expose this exercise to a LAN or rely on a firewall to repair a broad definition.
Logs come before improvisation
Use compose ps --all for state, compose logs SERVICE for process output, and inspect for image, mounts, network, and ports. Use them in that order before changing YAML:
state -> logs -> inspect -> smallest justified correction -> recreate -> repeat checks
A port-allocation error can occur before the container starts, so preserve compose up output as well as logs. Collect available evidence before guessing.
Containers are boundaries you configure, not little secure computers
Do not mount a home directory, credential store, engine socket, or broad drive, and never use privileged: true as a repair. Drop unneeded capabilities, prefer read-only filesystems and supported non-root users. The probe below has no volume, port, or secret; Ollama receives only its model volume, private network, and loopback port.
The engine, mounts, capabilities, and kernel determine the real boundary. A Desktop VM does not make a shared folder or published port harmless, and access to a rootful Docker socket is especially consequential.
6. The worked example: a local AI service you can break and recover
Declare the stack
Create an empty directory named t11-l03-local-ai. It must not contain a real project, .env with credentials, private documents, or another Compose file.
Verified public exercise pins - last verified 2026-09-04. Docker Hub reported Ollama
0.33.3at multi-platform digestsha256:32931b46719f673c05fdbaa81ccb26da18ea4a1c57590a754874ab28ba269eb2and curl8.22.0atsha256:58adaa4e8dca9c988bae2aba4ab3434a0bb2da16bbe3f92dec39ec7785166777. The public Ollama library reportedgemma3:270m, approximately 292 MB, with model content IDe7d36fb2c3b3. Reverify all three identities against the linked primary registry pages before use; stop if a digest no longer resolves or organisational review requires another release. The model has separate terms that must be acceptable for the exercise.
In the empty directory, create .env with these executable public exercise values, or replace them with reviewed equivalents before continuing:
OLLAMA_IMAGE=docker.io/ollama/ollama:0.33.3@sha256:32931b46719f673c05fdbaa81ccb26da18ea4a1c57590a754874ab28ba269eb2
PROBE_IMAGE=docker.io/curlimages/curl:8.22.0@sha256:58adaa4e8dca9c988bae2aba4ab3434a0bb2da16bbe3f92dec39ec7785166777
MODEL_ID=gemma3:270m
OLLAMA_URL=http://ollama:11434
Use current official release documentation and your organisation's image review. A fixed version tag states release intent; the reviewed digest fixes the retrieved image content. These values are not secrets, but do not add credentials to this file.
Create compose.yaml in the same directory:
services:
ollama:
image: ${OLLAMA_IMAGE:?Set OLLAMA_IMAGE in .env}
ports:
- "127.0.0.1:11434:11434"
volumes:
- ollama-models:/root/.ollama
networks:
- ai-private
healthcheck:
test: ["CMD", "ollama", "list"]
interval: 5s
timeout: 5s
retries: 12
probe:
image: ${PROBE_IMAGE:?Set PROBE_IMAGE in .env}
depends_on:
- ollama
environment:
OLLAMA_URL: ${OLLAMA_URL}
entrypoint: ["/bin/sh", "-c"]
command:
- |
curl --fail --show-error --retry 12 --retry-connrefused --retry-delay 2 \
"$${OLLAMA_URL}/api/tags" >/dev/null
echo PROBE_READY
exec sleep 7200
read_only: true
tmpfs:
- /tmp
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
networks:
- ai-private
networks:
ai-private: {}
volumes:
ollama-models:
name: t11-l03-ollama-models
The probe's depends_on controls creation order, not application readiness. Its bounded retry loop performs the readiness check. $$ passes a literal dollar sign through Compose so the shell inside the probe reads OLLAMA_URL. If retries are exhausted, the probe exits and leaves a useful error in its logs instead of pretending the stack is healthy.
Shell variables override values in .env. Use a dedicated shell for this project and remove stale overrides before every configuration or fault-injection test. On macOS or Linux:
unset OLLAMA_IMAGE PROBE_IMAGE OLLAMA_URL
docker compose config --environment \
| grep -E '^(OLLAMA_IMAGE|PROBE_IMAGE|OLLAMA_URL)='
On Windows PowerShell:
Remove-Item Env:OLLAMA_IMAGE, Env:PROBE_IMAGE, Env:OLLAMA_URL -ErrorAction SilentlyContinue
docker compose config --environment |
Select-String '^(OLLAMA_IMAGE|PROBE_IMAGE|OLLAMA_URL)='
The three displayed values must exactly match .env. This project contains no secrets; never print config --environment from a project that may contain credentials. Validate and inspect the rendered model before starting:
docker compose config --quiet
docker compose config
docker compose pull
docker compose up -d
docker compose ps --all
docker compose logs --tail 50 probe
Pass the rendered-configuration check only when it shows the two pinned image digests, OLLAMA_URL: http://ollama:11434, the named model volume, and 127.0.0.1:11434. Then pass startup only when both services are running, Ollama becomes healthy, and the probe log contains PROBE_READY. Inspect the host mapping and volume independently:
docker compose port ollama 11434
docker volume inspect t11-l03-ollama-models
docker inspect "$(docker compose ps -q ollama)"
The port result must begin with 127.0.0.1. In the inspect output, find the selected image, the named volume mounted at /root/.ollama, the project network, and the running state. Do not publish the probe and do not copy the complete inspect record into evidence if it contains unrelated metadata.
Record the resolved image identities using the appropriate engine's image-inspect command. A release tag records intent; the repository digest records what arrived. If a registry supplies no digest for a local image, record that limitation instead of inventing one.
Pull one model into the volume
Read only MODEL_ID from .env; do not source the file or export its Compose interpolation values. On macOS or Linux:
MODEL_ID="$(sed -n 's/^MODEL_ID=//p' .env)"
test -n "$MODEL_ID" || { echo 'MODEL_ID is missing' >&2; exit 1; }
docker compose exec ollama ollama pull "$MODEL_ID"
docker compose exec ollama ollama show "$MODEL_ID" >/dev/null
docker compose exec ollama ollama list
On Windows PowerShell:
$modelLine = Get-Content .env | Where-Object { $_ -match '^MODEL_ID=' }
if (($modelLine | Measure-Object).Count -ne 1) { throw 'Expected one MODEL_ID line' }
$env:MODEL_ID = $modelLine -replace '^MODEL_ID=', ''
docker compose exec ollama ollama pull $env:MODEL_ID
docker compose exec ollama ollama show $env:MODEL_ID
docker compose exec ollama ollama list
Both loaders accept only MODEL_ID; neither exports OLLAMA_URL or turns arbitrary .env lines into shell commands. The list must show the exact approved identifier and record its resolved model ID; for the verified fixture that ID starts with e7d36fb2c3b3. A host Ollama installation and the containerized Ollama service have separate storage unless you deliberately and safely connect them. This exercise downloads into the named volume rather than mounting a host application's private directory.
Lab framing: extract a synthetic record
Run the exact approved model and paste this fictional prompt when Ollama opens its prompt input:
Return JSON only with keys record_id, material, temperature_c, and result.
Use only the supplied synthetic note and do not infer missing values.
Synthetic note: Record LAB-C-17 tested a polymer film at 18 C.
Result: no visible change.
The accountable Lab reviewer checks that the response is valid JSON, contains exactly the four requested keys, preserves LAB-C-17, polymer film, 18, and no visible change, and adds no scientific claim. Exit the interactive model session after the check. A fluent response with a changed number or stronger conclusion is an application failure even when the container is healthy.
Company framing: classify a synthetic message
Use the same stack and exact model, but paste this fictional prompt:
Return JSON only with keys record_id, label, and basis.
Label must be ACTION or INFORMATION. Use only the supplied synthetic message.
Synthetic message: Record CO-C-17 says delivery remains scheduled for Tuesday.
No reply or approval is requested.
The accountable Company reviewer checks for valid JSON, exactly three keys, record CO-C-17, label INFORMATION, and a basis grounded in the absence of a requested reply or approval. Do not send or route anything. The model result is only a bounded operation check.
For either framing, run the model with:
docker compose exec ollama ollama run "$MODEL_ID"
In PowerShell, use $env:MODEL_ID. The infrastructure pass is: endpoint reachable, exact model present, and a non-error generation completes. The task pass is the human comparison above. Keep those conclusions separate. A healthy Ollama process can produce a wrong extraction, and a correct-looking answer does not prove the volume or port boundary.
Break the address and read the logs
Now create a controlled startup failure in the probe, not in the model data. Keep .env unchanged and create compose.break.yaml:
services:
probe:
environment:
OLLAMA_URL: http://ollama-typo:11434
This explicit override is deterministic even if a parent shell once held OLLAMA_URL. Recreate only the probe with both files and observe it after its retries finish:
docker compose -f compose.yaml -f compose.break.yaml config --quiet
docker compose -f compose.yaml -f compose.break.yaml up -d --force-recreate probe
docker compose -f compose.yaml -f compose.break.yaml ps --all
docker compose -f compose.yaml -f compose.break.yaml logs --tail 50 probe
The probe should exit, and its logs should report that the host cannot be resolved or reached. That is expected evidence. Do not add a public port, host networking, administrator mode, or a random IP. Compare the override with the service name in compose.yaml, return to the base declaration, and recreate only the probe. A plain restart is insufficient because Compose does not apply changed environment configuration on restart:
docker compose up -d --force-recreate probe
docker compose ps --all
docker compose logs --tail 20 probe
It passes when the probe is running again and logs contain PROBE_READY. One changed input, one visible failure, and one corrected rerun make the diagnosis reviewable.
Run the operation and recovery checks
First prove a normal stop is detectable. Stop Ollama, request the endpoint from inside the private network, then inspect before restoring it:
docker compose stop ollama
docker compose exec probe curl --fail --show-error http://ollama:11434/api/tags
docker compose ps --all
docker compose logs --tail 50 ollama
docker compose start ollama
docker compose exec probe curl --fail --show-error --retry 12 --retry-connrefused \
--retry-delay 2 http://ollama:11434/api/tags >/dev/null
docker compose exec ollama ollama show "$MODEL_ID" >/dev/null \
&& echo RECOVERY_PASS
The first curl must fail while Ollama is stopped. If it succeeds, verify that you targeted this project's service and did not reach another endpoint. After start, the retrying request must succeed before RECOVERY_PASS is printed. Run the selected synthetic model check again. This proves detection and recovery from a stopped service; it does not prove automatic failover or production availability.
On Windows PowerShell, perform the same stop and recovery proof explicitly:
docker compose stop ollama
docker compose exec probe curl --fail --show-error http://ollama:11434/api/tags
if ($LASTEXITCODE -eq 0) { throw 'Stopped Ollama was still reachable' }
docker compose ps --all
docker compose logs --tail 50 ollama
docker compose start ollama
docker compose exec probe curl --fail --show-error --retry 12 --retry-connrefused --retry-delay 2 http://ollama:11434/api/tags
if ($LASTEXITCODE -ne 0) { throw 'Ollama did not recover' }
docker compose exec ollama ollama show $env:MODEL_ID
if ($LASTEXITCODE -ne 0) { throw 'Approved model is unavailable after recovery' }
'RECOVERY_PASS'
Next prove the model survives the ordinary restart path:
docker compose restart ollama
docker compose exec probe curl --fail --show-error --retry 12 --retry-connrefused \
--retry-delay 2 http://ollama:11434/api/tags >/dev/null
docker compose exec ollama ollama show "$MODEL_ID" >/dev/null \
&& echo RESTART_PERSISTENCE_PASS
docker compose ps
On Windows PowerShell:
docker compose restart ollama
docker compose exec probe curl --fail --show-error --retry 12 --retry-connrefused --retry-delay 2 http://ollama:11434/api/tags
if ($LASTEXITCODE -ne 0) { throw 'Ollama did not become ready after restart' }
docker compose exec ollama ollama show $env:MODEL_ID
if ($LASTEXITCODE -ne 0) { throw 'Model did not survive restart' }
'RESTART_PERSISTENCE_PASS'
docker compose ps
Finally perform the stronger replacement check. Capture the current Ollama container ID, remove only that service container, recreate it from the declaration, and compare IDs:
before="$(docker compose ps -q ollama)"
docker compose rm --stop --force ollama
docker compose up -d ollama
after="$(docker compose ps -q ollama)"
printf 'before=%s\nafter=%s\n' "$before" "$after"
docker compose exec probe curl --fail --show-error --retry 12 --retry-connrefused \
--retry-delay 2 http://ollama:11434/api/tags >/dev/null
mount="$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/root/.ollama"}}{{.Name}} -> {{.Destination}}{{end}}{{end}}' "$after")"
test "$before" != "$after" \
&& test -n "$before" \
&& test -n "$after" \
&& test "$mount" = 't11-l03-ollama-models -> /root/.ollama' \
&& docker compose exec ollama ollama show "$MODEL_ID" >/dev/null \
&& printf 'REPLACEMENT_PERSISTENCE_PASS model=%s mount=%s\n' "$MODEL_ID" "$mount"
docker compose up -d probe
docker compose port ollama 11434
docker compose ps
The IDs must differ, the exact model must still be present, both services must return to running, and the synthetic task must still complete. The old container did not carry the durable model; the named volume did.
PowerShell uses the same sequence with PowerShell variables and checks:
$before = docker compose ps -q ollama
docker compose rm --stop --force ollama
docker compose up -d ollama
$after = docker compose ps -q ollama
"before=$before"
"after=$after"
if ([string]::IsNullOrWhiteSpace($before) -or [string]::IsNullOrWhiteSpace($after)) { throw 'Missing container ID' }
if ($before -eq $after) { throw 'Container was not replaced' }
docker compose exec probe curl --fail --show-error --retry 12 --retry-connrefused --retry-delay 2 http://ollama:11434/api/tags
if ($LASTEXITCODE -ne 0) { throw 'Ollama did not become ready' }
docker compose exec ollama ollama show $env:MODEL_ID
if ($LASTEXITCODE -ne 0) { throw 'Model did not survive replacement' }
$mount = docker inspect --format '{{range .Mounts}}{{if eq .Destination "/root/.ollama"}}{{.Name}} -> {{.Destination}}{{end}}{{end}}' $after
if ($mount -ne 't11-l03-ollama-models -> /root/.ollama') { throw "Unexpected model mount: $mount" }
"REPLACEMENT_PERSISTENCE_PASS model=$env:MODEL_ID mount=$mount"
docker compose up -d probe
docker compose port ollama 11434
docker compose ps
When finished, docker compose down removes this project's containers and network but preserves t11-l03-ollama-models. Keep it only if its owner and cleanup date are known. To delete this course model data later, first inspect the exact volume and then deliberately run docker volume rm t11-l03-ollama-models while no container uses it. Do not use compose down --volumes as an unexamined shortcut.
7. What goes wrong
Data lives only inside the container
Symptom: a model, index, configuration, or generated file disappears after the service container is replaced.
Fix: identify the application's actual durable path, mount a named volume there, and test replacement rather than restart alone. Decide separately whether that volume needs backup and restore.
The host port is already in use
Symptom: compose up reports that it cannot bind 127.0.0.1:11434.
Fix: preserve the error, identify the existing listener with the OS command from section 4, and decide which approved service owns the port. Stop only your own duplicate or choose another loopback port and update every recorded client address. Never "fix" the collision by binding all interfaces.
The instructions are followed without reading logs
Symptom: the probe repeatedly exits, while the YAML, image, network, and firewall are changed together.
Fix: run compose ps --all, then service-specific logs, then inspect. Change one evidenced cause and recreate only the affected service. Keep the failed output until the corrected run passes.
Everything runs as root or privileged
Symptom: a permissions error is answered with sudo, privileged: true, a Docker-socket mount, or a broad writable host mount.
Fix: stop. Confirm the engine boundary, image documentation, expected user, exact needed path, and ownership. Prefer a rootless engine where supported, non-root images, dropped capabilities, read-only roots, and narrow named volumes. Some model images require specific internal ownership; do not invent an arbitrary user that prevents startup, but do record and review the exception.
Images use floating or ambiguous names
Symptom: the same Compose file pulls different content next week, or a short image name resolves from an unexpected registry.
Fix: use a fully qualified approved registry path, pin a reviewed release, record the resolved digest, and test upgrades deliberately. Never treat popularity, a tutorial, or latest as provenance review.
Running is mistaken for ready
Symptom: Compose lists Ollama as running, but the probe or model request fails because startup is incomplete.
Fix: use a bounded readiness check against the function you need. Separate process state, API readiness, exact model presence, and task output. depends_on alone does not establish all four.
Cleanup deletes the volume
Symptom: down --volumes is copied from a tutorial and removes the only model copy or application data.
Fix: omit --volumes during routine container cleanup. Inspect the exact volume, its owner, contents, backup decision, and dependencies before deliberate deletion.
A local service is accidentally shared
Symptom: the port mapping starts with 0.0.0.0, lacks a host address, or responds from another device.
Fix: stop the stack, restore the explicit 127.0.0.1 binding, inspect the effective port mapping, and retest. Authentication, TLS, firewall policy, and team access belong to a reviewed later deployment, not this localhost exercise.
8. Do it yourself: a 90-minute deletion and recovery drill
Minutes 0-10: choose Docker or Podman and Lab or Company. Confirm the local non-production connection, available memory and disk, approved image references, exact model identifier, and that loopback port 11434 is free. Name the person who may delete the practice volume.
Minutes 10-22: create the empty project directory, .env, and compose.yaml. Read every image, mount, network, environment value, command, capability, and port. Reject floating tags, broad host mounts, engine-socket mounts, real data, secrets, and non-loopback publication.
Minutes 22-32: validate the rendered Compose model, pull the images, start the stack, and run compose ps --all. Require both services running, Ollama healthy, and PROBE_READY in probe logs. Inspect the Ollama container and named volume.
Minutes 32-47: pull the exact approved model into the volume. Run one synthetic prompt from the selected framing. Check endpoint operation, model identity, response completion, JSON shape, exact fixture values, and absence of invented claims separately.
Minutes 47-58: create compose.break.yaml with only the misspelled OLLAMA_URL, recreate the probe with both Compose files, and retain the expected exited state and diagnostic log long enough to inspect them. Return to the unchanged base declaration, recreate the probe, and require PROBE_READY.
Minutes 58-68: stop Ollama. Prove the internal API call fails, inspect state and logs, start Ollama, and wait for the bounded API check to pass. Rerun the synthetic prompt and do not claim recovery until both infrastructure and task checks pass.
Minutes 68-78: restart Ollama and prove the exact model remains. Then record the old container ID, remove only the Ollama service container, recreate it, and prove the ID changed while ollama show still succeeds. Bring the probe back to running.
Minutes 78-86: arrange one terminal view containing the different before/after container IDs, final REPLACEMENT_PERSISTENCE_PASS line with model and mount, loopback-only compose port result, and compose ps rows for the two running services. Keep unrelated workloads and private paths out of view.
Minutes 86-90: capture the single screenshot defined below. Read it at normal size, remove or recapture any secret, private path, hostname, unrelated container, or real prompt. Run compose down without --volumes after capture if the stack is no longer needed, then inspect and schedule deliberate removal of the course volume.
9. Exit check
Deliver exactly one artifact: one evidence screenshot showing the two-service local AI stack running after the Ollama service container was replaced, with the terminal line REPLACEMENT_PERSISTENCE_PASS visible.
The screenshot passes only when it shows different non-empty before/after container IDs; the pass line names the exact model and t11-l03-ollama-models -> /root/.ollama; the visible compose port result begins with 127.0.0.1; and compose ps identifies both ollama and probe as running. The completed operation, deliberate probe failure, stop/start recovery, and ordinary restart check must have passed during the same run, but do not submit separate screenshots, logs, YAML, or notes as additional artifacts.
Crop or recapture so the single image contains no credentials, tokens, personal data, real prompts, private hostnames, unrelated workloads, or broad filesystem paths. A screenshot of a chat answer, a running row without the persistence line, or a volume list without both running services does not pass.
10. Rule to remember
If it is not in a volume, it is not saved.
11. Further reading & tools
- Taught: Deploy & Run Applications with Docker - separates images, containers, registries, volumes, and networks.
- Taught: First container lifecycle - focused practice for named-container lifecycle operations.
- Taught: Logs, inspect, and terminal - establishes the state, logs, inspect diagnosis order.
- Taught: Volumes - tests persistence independently from a container's lifecycle.
- Taught: Networks - distinguishes private service discovery from host publication.
- Taught: Compose guestbook - operates and cleans up one bounded multi-service project.
- Catalogued: Docker on Windows, Docker on macOS, and Docker on Linux - host-specific engine, mount, port, and privilege references.
- Catalogued: Evidence screenshots - guidance for one narrow, privacy-reviewed visual claim.
- Catalogued · Primary: Docker Compose quickstart (opens in a new tab), environment interpolation (opens in a new tab), and restart (opens in a new tab) - official lifecycle, precedence, and restart behavior.
- Catalogued · Primary: Docker volumes (opens in a new tab), container logs (opens in a new tab), and Engine security (opens in a new tab) - official persistence, diagnosis, and security references.
- Catalogued · Primary: Podman Compose (opens in a new tab), Podman volumes (opens in a new tab), and Podman logs (opens in a new tab) - verify the provider and current command behavior.
- Catalogued · Primary: Ollama Docker guidance (opens in a new tab), Ollama API introduction (opens in a new tab), and Gemma 3 270M model record (opens in a new tab) - current container, endpoint, and model provenance.
- Catalogued · Primary: Ollama image tags (opens in a new tab) and curl image tags (opens in a new tab) - recheck release tags and resolved multi-platform digests before use.
- Catalogued:
T11-L04· Your own AI server - continues from a local operable stack to a protected server deployment with TLS and staging. - Catalogued: Dockge - a visual Compose manager to assess only after you can operate and recover the underlying stack directly.