At Level 4 Integrator, company or lab systems and real data can depend on infrastructure you operate. Moving a working local stack to a server is not a copy operation. It adds provider accounts, DNS, Internet routes, certificates, individual access, patching, release evidence, staging, and an outage that affects colleagues rather than only you.
2. It needs to work at 08:00 without your laptop
The local assistant from T11-L03 answers a synthetic prompt and keeps its model after a container is replaced. Eight colleagues now need the endpoint for a morning workflow. You rent a server, publish the model port, and send everyone the IP address. It works from home that evening.
At 08:00 the next morning, the endpoint is unreachable after an automatic host restart. When it returns, a crawler is already requesting it. There is no TLS, no authentication, no deployment record, and no distinction between a candidate configuration and the one colleagues use. The domain and provider account belong to one person's private account. Nobody else knows which image, model, firewall, or restart step is correct.
The production problem is not "make Ollama listen on the Internet." It is "operate one reviewable service through one controlled front door." You will provision a right-sized approved host, keep model ports private, route only two API operations through Caddy over HTTPS, give people individual credentials, deploy to staging before production, and verify from outside that no other public port answers.
3. After this you can
- Select and provision an approved server region and size from measured workload needs.
- Deploy one versioned container stack without publishing its model backends.
- Terminate TLS at one reverse proxy and expose only the required API methods and paths.
- Separate staging and production data, credentials, names, volumes, and release decisions.
- Diagnose and verify the server from both the host and an authorised external device.
4. Prerequisites
T11-L03- Containers, honestly, including images, volumes, private networks, logs, lifecycle, and replacement tests.T12-L04- Put a lock on it. Complete its external audit before inviting colleagues or connecting approved real data.- An organisational or institute provider account, billing approval, an approved server region, a domain controlled by the organisation, and a named service owner, platform operator, security contact, and substitute operator.
- A Linux server supported by your organisation, Docker Engine with Compose or an approved equivalent, Caddy-compatible inbound networking, and an administrator route restricted to an approved source network or VPN.
- Two DNS names, such as
ai-staging.example.organdai.example.org, plus permission to create records and obtain certificates for both. - One reviewed model that fits with headroom under the expected concurrent synthetic test load.
Use only synthetic prompts until the service's purpose, access, retention, egress, backup, and organisational approvals cover the intended data. Do not put provider tokens, DNS credentials, user passwords, password hashes, SSH keys, private hostnames, or production documents in Git, Compose YAML, screenshots, shell history, or chat. An EU server location can support a deployment decision, but it does not by itself establish compliance or constrain every support, backup, logging, model-download, or subprocessor path.
5. The idea in one page
One proxy is the public boundary
The server has one public application edge:
authorised external client
| HTTPS :443 (and optional HTTP :80 redirect/certificate flow)
v
Caddy
|-- staging identity -> staging model service -> staging volume
`-- production identity -> production model service -> production volume
administrator -> restricted VPN/source -> SSH
Internet -X-> model ports, container engine, database, dashboards, metrics
Only Caddy publishes host ports. The Ollama services use private Compose networks and no ports entry. Authentication happens before proxying. Path and method matchers expose GET /api/tags and POST /api/generate, not model pull, deletion, copy, or administrative operations. The provider and host firewalls independently permit the intended edge and deny everything else.
TLS proves that the client reached a holder of a certificate valid for the requested name; it does not prove the user is authorised or the model is correct. Authentication proves an accepted credential; it does not approve every prompt. The endpoint still needs task-level data rules, output checks, and human judgement.
Size from measurements, not labels
Record the selected model's disk use, idle memory, peak memory for the longest approved synthetic prompt, response latency, and expected concurrency. Add operating-system, runtime, and failure headroom. A "GPU server" label is not a sizing result. Start with staging and a smaller approved model if the measured workload does not justify an accelerator. T11-L05 handles concurrency engines and cost comparison; this book establishes a safe, operable first server, not a claim of scale.
Staging is separate authority
Staging and production may share one host for this bounded exercise, but they do not share a hostname, credential set, model volume, service name, or release decision. Staging uses synthetic data only. A staging credential cannot authenticate to production. Production is not started until staging passes. A stronger organisational design can use separate hosts or accounts; document the residual risk if one host failure can affect both environments.
Promote identified artifacts: the same reviewed Compose and Caddy configuration revision, image digest, and model identifier tested in staging. Do not rebuild an image, retype proxy rules, or change a moving latest tag during promotion. Configuration is part of the release.
Deployment must outlive terminal history
Keep the secret-free deployment declaration in a controlled repository. Keep runtime secrets in the approved host secret store or root-readable files outside the checkout. A release record names commit, images and resolved digests, model, environment, operator, checks, and rollback target. The deployment procedure validates, pulls, starts only named services, checks health, and records what actually arrived.
The provider account, domain, and certificate lifecycle must belong to the organisation rather than a personal account. Give a substitute the authorised administrative route and runbook. A service that only its builder can restart has not been handed over.
6. The worked example: staging and production behind Caddy
The Lab framing deploys to an institute-controlled server reachable by colleagues through its approved network path. The Company framing uses an approved rented EU VPS. The stack and tests are the same; the provider responsibility, data approval, and network audience differ.
Write the launch record before provisioning
Create one approved deployment record outside public repositories:
Service: Synthetic team AI endpoint
Purpose: bounded text generation through GET /api/tags and POST /api/generate
Provider account owner / billing owner: [organisational roles]
Server region / backup region / support access: [verified values]
Service owner / operator / substitute: [approved identities]
Staging name: [approved DNS name]
Production name: [approved DNS name]
Administrative source: [approved VPN or fixed CIDR]
Public application ports: tcp/443; tcp/80 only for redirect or certificate flow
Denied public ports: tcp/22 outside admin source; tcp/11434; all unspecified ports
Compose commit: [full commit]
Caddy image / digest: [reviewed release and resolved digest]
Ollama image / digest: [reviewed release and resolved digest]
Model ID / model content ID: [exact identifiers]
Staging volume / production volume: t11-l04-models-staging / t11-l04-models-production
Rollback target: [last known-good commit, images, config, and model]
Patch owner / cadence / next date: [role, cadence, date]
Backup owner / restore test: [role, isolated target, date]
Decision: BLOCKED until staging, external, restore, and substitute tests pass
Provision from the organisation's provider account in the approved region. Attach a provider firewall before installing the workload. Permit 443 publicly; permit 80 only if your certificate or redirect design needs it; permit 22 only from the approved administrative source. Preserve that route before applying host rules. Do not expose a deployment dashboard, Docker socket, Ollama port, database, or metrics endpoint.
Size the host from the local measurements. Record CPU or accelerator type, memory, disk, expected eight-user request pattern, and headroom. Provider snapshots and backups can be useful, but confirm their location, encryption, retention, access, and restore owner rather than treating a checkbox as recovery.
Declare the stack without secrets
Create an access-controlled deployment directory such as /opt/t11-l04-ai-server. Save this secret-free .env alongside the reviewed files:
CADDY_IMAGE=caddy:2.10.2-alpine
OLLAMA_IMAGE=ollama/ollama:0.33.3@sha256:32931b46719f673c05fdbaa81ccb26da18ea4a1c57590a754874ab28ba269eb2
MODEL_ID=gemma3:270m
STAGING_HOST=ai-staging.example.org
PRODUCTION_HOST=ai.example.org
These public exercise values were last verified for the neighboring container lesson on 2026-09-04. Recheck official registry and model records, licensing, architecture support, and organisational approval before use. Record resolved image and model content identities after pull. A fixed release states intent; a digest identifies retrieved image content.
The two example.org names are reserved examples. Replace them with the two approved organisational DNS names before starting Caddy; never request certificates or create DNS for the example values.
Save this as compose.yaml:
services:
caddy:
image: ${CADDY_IMAGE:?Set CADDY_IMAGE}
restart: unless-stopped
environment:
STAGING_HOST: ${STAGING_HOST:?Set STAGING_HOST}
PRODUCTION_HOST: ${PRODUCTION_HOST:?Set PRODUCTION_HOST}
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- /etc/t11-l04/users:/etc/caddy/users:ro
- /etc/t11-l04/sites-enabled:/etc/caddy/sites-enabled:ro
- caddy-data:/data
- caddy-config:/config
networks:
- proxy-public
- staging-edge
- production-edge
read_only: true
tmpfs:
- /tmp
security_opt:
- no-new-privileges:true
ollama-staging:
image: ${OLLAMA_IMAGE:?Set OLLAMA_IMAGE}
restart: unless-stopped
volumes:
- models-staging:/root/.ollama
networks:
- staging-edge
read_only: true
tmpfs:
- /tmp
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD", "ollama", "list"]
interval: 10s
timeout: 5s
retries: 12
ollama-production:
image: ${OLLAMA_IMAGE:?Set OLLAMA_IMAGE}
restart: unless-stopped
volumes:
- models-production:/root/.ollama
networks:
- production-edge
read_only: true
tmpfs:
- /tmp
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD", "ollama", "list"]
interval: 10s
timeout: 5s
retries: 12
networks:
proxy-public: {}
staging-edge:
internal: true
production-edge:
internal: true
volumes:
caddy-data:
name: t11-l04-caddy-data
caddy-config:
name: t11-l04-caddy-config
models-staging:
name: t11-l04-models-staging
models-production:
name: t11-l04-models-production
There is no ports entry on either Ollama service. Caddy can reach each service only through the corresponding network. The two model stores have different names and lifecycles. restart improves ordinary host recovery but does not replace monitoring, patching, backup, or a substitute operator.
Both model networks are internal, so the running model services have no ordinary Internet route. For the separately approved model-download window, save this temporary override as compose.model-pull.yaml:
services:
ollama-staging:
networks:
- staging-edge
- model-pull
ollama-production:
networks:
- production-edge
- model-pull
networks:
model-pull: {}
Use it only around the named model pull, then recreate the affected service from base compose.yaml and verify that model-pull is absent. The container engine's separate image pull also needs an approved registry route; runtime network isolation does not govern the engine daemon.
Save this as Caddyfile:
(common) {
encode zstd gzip
header {
X-Content-Type-Options nosniff
Referrer-Policy no-referrer
-Server
}
}
(staging_auth) {
basic_auth {
import users/staging
}
}
(production_auth) {
basic_auth {
import users/production
}
}
import sites-enabled/*
Create sites-available/staging.caddy in the deployment repository:
{$STAGING_HOST} {
import common
import staging_auth
@tags {
method GET
path /api/tags
}
handle @tags {
reverse_proxy ollama-staging:11434
}
@generate {
method POST
path /api/generate
}
handle @generate {
reverse_proxy ollama-staging:11434
}
respond "Not found" 404
}
Create sites-available/production.caddy beside it:
{$PRODUCTION_HOST} {
import common
import production_auth
@tags {
method GET
path /api/tags
}
handle @tags {
reverse_proxy ollama-production:11434
}
@generate {
method POST
path /api/generate
}
handle @generate {
reverse_proxy ollama-production:11434
}
respond "Not found" 404
}
The root-managed /etc/t11-l04/sites-enabled directory is the activation boundary. Install only staging.caddy there for the staging gate. Caddy therefore has no production site to serve and does not request its certificate early. After staging approval, install the reviewed production.caddy from the same commit and reload. Record the enabled-file hashes in each release.
Basic authentication is intentionally small and inspectable for this exercise. Every colleague gets a unique account; never share one service password. An organisational identity-aware proxy with MFA and central revocation is preferable when approved. If you substitute one, retain the same logged-out denial, individual identity, revocation, path, and backend-port tests.
Create password hashes interactively so plaintext passwords never appear as command arguments. Use the exact reviewed Caddy image:
docker run --rm -it caddy:2.10.2-alpine caddy hash-password
Repeat per synthetic test identity and approved user. Put one username hash pair per line in /etc/t11-l04/users/staging or /etc/t11-l04/users/production. Keep both files outside the repository, owned by root, readable only through the host and container arrangement reviewed for the platform, and mounted read-only. Treat hashes as credential material. Use the approved password manager to distribute unique plaintext passwords. Revocation removes one identity from the appropriate root-managed file, validates Caddy, reloads it, and verifies that the removed account receives 401 while another account still works. Because hashes are file-mounted rather than environment values, docker compose config and container inspection do not render them.
Validate and release staging first
Do not print rendered environment values into shared CI logs. On the server, validate declarations, pull exact images, and inspect resolved identities:
cd /opt/t11-l04-ai-server
sudo install -d -o root -g root -m 700 /etc/t11-l04/sites-enabled
sudo install -o root -g root -m 644 \
sites-available/staging.caddy /etc/t11-l04/sites-enabled/staging.caddy
docker compose config --quiet
docker compose pull caddy ollama-staging
docker image inspect "$(docker compose images -q caddy)" --format '{{index .RepoDigests 0}}'
docker image inspect "$(docker compose images -q ollama-staging)" --format '{{index .RepoDigests 0}}'
docker compose up -d caddy ollama-staging
docker compose ps
docker compose logs --tail 100 caddy ollama-staging
The server-side pass is: Caddy and staging are running, staging becomes healthy, no production container has started, and logs contain no repeated crash, certificate, permission, or upstream-resolution error. Inspect publication independently:
docker compose port caddy 443
docker compose port ollama-staging 11434
docker compose port ollama-production 11434
ss -lntup
Caddy must report host publication. Both Ollama port commands must return no mapping. ss must show no public listener for 11434. Review all other listeners with the host owner; do not ignore one because its port number is unfamiliar.
Load only the public model identifier and pull the approved model into staging:
MODEL_ID="$(sed -n 's/^MODEL_ID=//p' .env)"
test -n "$MODEL_ID"
docker compose -f compose.yaml -f compose.model-pull.yaml \
up -d --force-recreate ollama-staging
docker compose -f compose.yaml -f compose.model-pull.yaml \
exec ollama-staging ollama pull "$MODEL_ID"
docker compose up -d --force-recreate ollama-staging
docker compose exec ollama-staging ollama show "$MODEL_ID" >/dev/null
docker compose exec ollama-staging ollama list
docker inspect "$(docker compose ps -q ollama-staging)" \
--format '{{range $name, $_ := .NetworkSettings.Networks}}{{$name}} {{end}}'
The final network list must contain only the project's staging edge and not model-pull. Record the model content ID shown by the runtime. Do not silently select another model when a pull, licence, memory, or architecture check fails.
Create staging DNS in the organisational account. Point only the staging name at the reviewed server addresses. If an AAAA record exists, every firewall and external test must cover IPv6 as well as IPv4. Wait for DNS, then validate the live certificate without curl --insecure:
curl --fail --silent --show-error "https://$STAGING_HOST/" --output /dev/null
openssl s_client -connect "$STAGING_HOST:443" -servername "$STAGING_HOST" \
-verify_return_error </dev/null 2>&1 | grep 'Verify return code: 0'
The unauthenticated curl --fail is expected to fail with HTTP 401; that is a denial pass, not an outage. The OpenSSL command must report return code 0. Create a permission-600 netrc file on the authorised test device with only the staging test identity, then run:
curl --fail --silent --show-error --netrc-file "$NETRC_FILE" \
"https://$STAGING_HOST/api/tags"
curl --fail --silent --show-error --netrc-file "$NETRC_FILE" \
-H 'Content-Type: application/json' \
--data "{\"model\":\"$MODEL_ID\",\"prompt\":\"Reply with exactly SERVER_READY\",\"stream\":false}" \
"https://$STAGING_HOST/api/generate"
Confirm the tags response names the exact model and the generation response is valid JSON with a non-empty response. The words can differ; infrastructure success does not prove instruction-following quality. Test one blocked operation too: authenticated POST /api/pull must return 404, and no new model may appear.
Record the Compose commit, image digests, model ID, Caddy configuration hash, certificate name and issuer, checks, operator, time, and known-good rollback target. Do not store passwords, hashes, netrc content, raw prompts, or full logs in the release record.
Promote the same revision to production
After owner approval, create production DNS and unique production accounts. Pull and start the production backend from the same reviewed declarations, install the reviewed production site, and reload only after validation:
docker compose pull ollama-production
docker compose up -d ollama-production
docker compose -f compose.yaml -f compose.model-pull.yaml \
up -d --force-recreate ollama-production
docker compose -f compose.yaml -f compose.model-pull.yaml \
exec ollama-production ollama pull "$MODEL_ID"
docker compose up -d --force-recreate ollama-production
docker compose exec ollama-production ollama show "$MODEL_ID" >/dev/null
sudo install -o root -g root -m 644 \
sites-available/production.caddy /etc/t11-l04/sites-enabled/production.caddy
docker compose exec caddy caddy validate --config /etc/caddy/Caddyfile
docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile
docker compose ps
The image digest, model identifier, root Caddyfile, and staging site must match staging evidence; the production site addition must come from that reviewed commit and have its own recorded hash. The model volumes and credentials must not match. Repeat certificate, logged-out denial, authenticated tag, synthetic generation, blocked pull, and external-port checks against production. Start with the two test identities; add the remaining approved individual users only after the external test passes. Remove temporary staging access at the end of its approved window.
Verify from outside, including every TCP port
Only scan a host you own or have explicit permission to test. Run from an authorised device outside the provider network and outside the SSH allow-list. A full TCP scan can take time and may trigger alerts; coordinate it with the security or provider owner. Use the production DNS name and test every published address.
curl --silent --show-error --output /dev/null --write-out 'unauth=%{http_code}\n' \
"https://$PRODUCTION_HOST/api/tags"
openssl s_client -connect "$PRODUCTION_HOST:443" -servername "$PRODUCTION_HOST" \
-verify_return_error </dev/null 2>&1 | grep 'Verify return code: 0'
curl --fail --silent --show-error --netrc-file "$NETRC_FILE" \
"https://$PRODUCTION_HOST/api/tags" > tags.json
python3 -c 'import json; d=json.load(open("tags.json")); assert d.get("models"); print("MODEL_PASS")'
nmap -Pn -p- --reason "$PRODUCTION_HOST"
Expected evidence is unauth=401, certificate verify return code 0, MODEL_PASS, and an nmap result whose only open TCP ports are 443 and, if deliberately enabled at the proxy, 80. Port 22 must be filtered from this source. Ports 11434, 3000, container-engine ports, dashboards, databases, and every unspecified port must not be open. If DNS has an AAAA record, repeat with nmap -6 -Pn -p- and an IPv6-capable external path. Absence of IPv6 connectivity is not proof of a filtered IPv6 address.
Open the HTTPS endpoint in a clean browser as a final human check. It must show a valid certificate and an authentication challenge; after individual login, only the allowed API paths work. Do not capture a password prompt containing a typed password, browser storage, private IP, or credential file.
Lab and Company decisions
For the Lab, institute hardware and the institute identity/network path remain under named institutional ownership. Restrict the audience to the institute VPN or approved research network where possible. Arrange reviewed images and models through an internal registry or transfer route when unrestricted egress is prohibited. Do not infer that unpublished or participant data is approved merely because the host is on premises.
For the Company, record the VPS provider entity, EU region, backups, snapshots, support access, subprocessors, billing account, abuse contact, and data path. Provider firewall and host firewall both apply. Keep the domain and provider project in company accounts. A private employee account is not a continuity plan.
For either framing, patch first in staging. Before an image, host package, Caddy, model, firewall, or authentication change, record rollback, back up necessary volumes and configuration, apply the change to staging, rerun synthetic functional and denied tests, then schedule production. A backup counts only after an isolated restore verifies the exact model, proxy configuration, individual login, and synthetic request. Never restore over production to test recovery.
7. What goes wrong
The model port is public
Symptom: docker compose port or an external scan shows 11434, or direct requests bypass authentication.
Fix: stop public traffic, remove the backend ports mapping, confirm the proxy network route, close provider and host firewall rules, recreate the service, and repeat external IPv4 and IPv6 scans. An obscure port is not protection.
TLS works but authentication does not
Symptom: a logged-out GET /api/tags returns model data, or a removed user still authenticates.
Fix: contain the route, inspect the active Caddy configuration, restore authentication before proxy handlers, reload only after validation, and test 401, permitted login, and revoked login separately. Rotate affected credentials if exposure is possible.
Staging is production with a different label
Symptom: both names use the same user list, volume, mutable model tag, or real dataset.
Fix: separate authority, not just hostnames. Use distinct credentials, volumes, DNS names, service names, data, and release decisions. Record shared-host risk or provision a separate host when the consequence requires it.
Deployment lives in shell history
Symptom: recovery depends on commands the builder remembers, and nobody can identify the active configuration or image.
Fix: version the secret-free declarations, create an exact release record and runbook, restrict direct changes, and have the substitute deploy or roll back the synthetic staging service from the record.
The server works locally but not externally
Symptom: Caddy answers on the host while the public name times out or presents the wrong certificate.
Fix: collect evidence in order: compose ps, service logs, ss, Caddy validation, DNS A and AAAA, provider firewall, host firewall, then external TLS. Change one evidenced layer at a time. Do not publish Ollama as a diagnostic shortcut or use --insecure to hide certificate failure.
The model does not fit at 08:00
Symptom: one request passes, but eight synthetic concurrent requests cause swapping, timeouts, or restarts.
Fix: preserve metrics, reduce prompt and model demand, cap concurrency, or resize from measurements. Do not buy hardware from a single-request result. Complete T11-L05 before claiming shared-service capacity.
Nobody patches or owns the domain
Symptom: notices go to a departed person's mailbox, the domain sits in a personal registrar account, or upgrades happen only after an incident.
Fix: transfer accounts to organisational ownership, name primary and substitute operators, schedule host and application review, and test access before the original builder is unavailable.
Automatic restart hides a crash loop
Symptom: restart: unless-stopped repeatedly restarts a service while users receive errors and logs rotate away.
Fix: alert on health and restart count, retain bounded logs under policy, stop the affected route when repeated failure crosses its threshold, and diagnose the first error. Restart policy is not self-healing proof.
8. Do it yourself: deploy and verify in 120 minutes
Minutes 0-15: choose Lab or Company, name owners and substitutes, confirm provider and domain ownership, record region and data paths, and calculate a server size from one reviewed model's measured disk, memory, latency, and expected synthetic load. Confirm T12-L04 is complete.
Minutes 15-30: provision the approved host with provider firewall attached. Preserve the restricted administrative route. Permit only proxy ports publicly, create no public dashboard, and record IPv4 and IPv6 addresses. Create staging DNS only.
Minutes 30-48: create the deployment directory, secret-free .env, compose.yaml, and Caddyfile. Create unique staging credentials interactively in the root-managed mounted user file. Validate Compose and Caddy. Reject any backend port, shared credential, floating image, broad mount, or secret in the checkout.
Minutes 48-65: pull reviewed images, start only Caddy and staging, pull the exact model into the staging volume, and record resolved image and model identities. Require healthy services, private backend ports, bounded logs, and the expected host listeners.
Minutes 65-80: from an authorised external device, verify valid staging TLS, logged-out 401, individual authenticated tags, one synthetic generation, denied /api/pull, and all-port IPv4 and IPv6 results where applicable. Fix the smallest evidenced boundary failure and rerun the complete set.
Minutes 80-95: record the staging evidence and owner approval. Create production DNS and unique production credentials. Start production from the same reviewed commit, image digest, model ID, and Caddy revision, but with its separate volume and identities.
Minutes 95-108: repeat the external tests against production. Test an individual credential revocation and restoration with a synthetic user. Verify port 22 is unavailable from the colleague path and that no backend, engine, dashboard, or database port is public.
Minutes 108-116: reboot only within the approved maintenance window. Require Caddy and both selected services to return, certificates to remain valid, exact models to remain present, and the external synthetic test to pass. Have the substitute locate logs and restart one staging service from the runbook.
Minutes 116-120: arrange one privacy-reviewed screenshot containing the production HTTPS origin with valid certificate indication and the external terminal's full-scan summary showing no open port except the proxy's 443 and optional 80. Exclude credentials, real prompts, host administration details, private addresses, and unrelated services.
9. Exit check
Deliver exactly one artifact: one evidence screenshot showing the stack reachable from an authorised external device over HTTPS with a valid certificate and no open public port except the reverse proxy's 443 and optional 80.
It passes only after the release record shows the exact Compose commit, image digests, model ID, separate staging and production identities and volumes, staging approval, logged-out denial, individual authenticated synthetic request, denied management path, IPv4 and applicable IPv6 full-port scans, restart recovery, credential revocation, and substitute operation. In the image, the production hostname and valid-certificate state must be visible alongside the external scan summary; 22, 11434, deployment panels, databases, and unspecified ports must not be open.
The release record, configuration, logs, and scan files support that one screenshot; do not submit them as additional exit artifacts. A screenshot made from the server itself, a browser used with certificate warnings, a partial common-port scan, or a view containing credentials does not pass.
10. Rule to remember
One front door, and it is the proxy.
11. Further reading & tools
- Taught:
T11-L03- Containers, honestly - supplies the image, volume, network, logs, lifecycle, and persistence controls used on the server. - Taught:
T12-L04- Put a lock on it - supplies the five-step external audit, authentication, secret, egress, logging, and restore requirements that gate exposure. - Taught: Self-hosting with Dokploy on Hetzner - maps provider, control-plane, application, route, ownership, and recovery responsibilities.
- Taught: Self-host on a private cloud - defines separate environment authority, immutable promotion, release signals, and rollback targets.
- Taught: Troubleshooting - establishes evidence-first diagnosis across engine, workload, port, and target layers.
- Taught: Caddy automatic HTTPS (opens in a new tab) - primary reference for certificate automation and HTTP-to-HTTPS behavior.
- Catalogued: Caddy
basic_auth(opens in a new tab) - current syntax and password-hash requirements for the bounded exercise; prefer approved central identity where available. - Catalogued: Local Kubernetes and Deploy to Kubernetes - optional cluster deployment paths; do not add Kubernetes merely to operate one host.
- Catalogued: AI Lab local LLM, AI Lab recipes, and InstructLab container - local model and recipe preparation references, not production exposure guidance.
- Catalogued: Docker Compose production guidance (opens in a new tab) - primary considerations for production Compose changes and redeployment.
- Catalogued: Hetzner Cloud firewalls (opens in a new tab) - provider-specific network control reference when that approved provider is selected.
- Catalogued:
T02-L04- A self-hosted assistant - applies the protected server boundary to a colleague-facing chat interface and recovery test. - Catalogued:
T11-L05- Serving at scale - continues with concurrency, throughput, quantisation, capacity, and total-cost measurements. - Catalogued: Tools index - compare approved proxies, providers, and deployment layers after ownership and boundaries are fixed.