Skip to content

#2319: Initial podman setup, more iterations follow#2321

Merged
jh-RLI merged 33 commits into
developfrom
feature-2319-add-podman-setup
Jul 23, 2026
Merged

#2319: Initial podman setup, more iterations follow#2321
jh-RLI merged 33 commits into
developfrom
feature-2319-add-podman-setup

Conversation

@jh-RLI

@jh-RLI jh-RLI commented May 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a reproducible, deterministic (Infrastructure-as-Code) container deployment for the OEP based on rootless Podman + systemd Quadlets, plus the documentation to install, update and maintain it. This is the setup used to move the OEP deployment to a new host environment, and it makes current and future releases straightforward to install.

What's included

Deployment (Podman Quadlets)

One systemd --user Quadlet unit per service (web app, PostgreSQL, Fuseki, Ontop, Lookup) on a shared oep container network with working inter-container DNS.
nginx reverse proxy terminating HTTPS on port 443, designed for an upstream re-encrypting TLS proxy; idempotent install-nginx.sh (self-signed backend cert, two-user/sudo aware).
Django reverse-proxy hardening via oep.env (OEP_BEHIND_TLS_PROXY, ALLOWED_HOSTS, scheme-normalised CSRF_TRUSTED_ORIGINS).
podman-compose kept as a local/dev alternative.
Self-provisioning images

App image bakes the OEO release + OEO-extended template before collectstatic.
Ontop image auto-downloads the PostgreSQL JDBC driver (build-arg JDBC_VERSION), bakes ontology + empty mapping, and is configured entirely via env (ONTOP_DB_*) — no ontop.properties.
Lookup config path resolved automatically (no symlink), production build workflow uses the ghcr image names.
Configuration

Single env file (oep.env) for secrets + host/HTTPS settings; server-specific config is scripted (install.sh, install-nginx.sh).
Documentation

New canonical "Production deployment (Podman)" mkdocs guide (Overview → Install → Ontop → Update → Maintenance), placeholders only.
Podman READMEs trimmed to point at the guide; docker/compose docs relabelled dev/CI-only; orphan ontop page retired; broken links fixed; Postgres version reconciled to 14; missing SPDX/REUSE headers added.
Changelog updated.
Deployment / release notes
This branch adds no new Django/Alembic migrations; app-container start runs migrate + alembic upgrade head automatically.
Until the app/ontop images are published to ghcr, build them locally under the ghcr names the units reference (see the Install guide) — the units use pull policy missing.
reuse lint should be run in CI / a dev env (the CLI wasn't available during development) to confirm full-tree REUSE compliance.
Out of scope / follow-ups (tracked separately)
Per-environment storage/volume-path placement (+ btrfs sub-volumes), splitting the data vs application DB, PG 14→15/16 upgrade, containerising the reverse proxy, wiring OEO/OEO-extended into Fuseki for the lookup index, and clearing the pre-existing mkdocs --strict warnings — all deliberately deferred to their own branches.

Verification

mkdocs build passes with no new warnings; new pages, mermaid diagrams and content tabs render.
Deployed and running on the staging host (serves 200 through the upstream-proxy → nginx → app chain).

Workflow checklist

Automation

Closes #2319

PR-Assignee

Reviewer

  • 🐙 Follow the
    Reviewer Guidelines
  • 🐙 Provided feedback and show sufficient appreciation for the work done

jh-RLI and others added 30 commits May 19, 2026 19:29
… README

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Provides an alternative to podman-compose for servers running a systemd
user session. Each service is a standalone Podman quadlet unit file that
systemd manages directly — no compose binary required at runtime.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…paths #2319

- Add podman/serviceConfigs/ontop/ with empty mapping.obda skeleton,
  ontop.properties.template, .gitignore (excludes ontology.owl,
  postgresql.jar, ontop.properties), and setup README
- Update podman-compose.yaml and oep-ontop.container to mount from
  podman/serviceConfigs/ontop/ instead of docker/serviceConfigs/ontop/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ages #2319

- Add .github/workflows/build-production-image.yaml to build and push
  ghcr.io/openenergyplatform/oeplatform-production (app + Vite build) and
  ghcr.io/openenergyplatform/oeplatform-ontop (Ontop + JDBC driver) on v* tags
- Update podman-compose.yaml and quadlet container files to pull from
  registry instead of building locally on the server
- Update quadlets/install.sh to reflect pull-based deployment

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>


- Add explicit container_name to postgres service so dnsname CNI plugin
  registers it as "postgres" (matches OEP_DJANGO_HOST / LOCAL_DB_HOST)
- Declare external oep network; set default_network=oep in containers.conf
  to work around podman-compose 1.5.0 not passing --network to podman run
- Add networks section with oep to all services (explicit long-form)
- Add lookup_index named volume; move lookup config to podman/serviceConfigs/
  to keep podman and docker setups fully separate
- Prefix all short image names with docker.io/ (fixes short-name resolution)
- Fix docker/Dockerfile.ontop: qualify FROM ontop/ontop as docker.io/ontop/ontop

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…2319

- Correct Podman version requirement from 4.0 to 3.4+
- Document CNI plugin version mismatch fix (containernetworking-plugins
  0.9.1 vs cniVersion 1.0.0): download v1.9.1 binaries to ~/.config/cni/plugins
- Document podman-compose 1.5.0 network assignment bug and workaround:
  pre-create oep network + set default_network in containers.conf
- Update all commands to use --env-file podman/.env
- Add Quadlets section explaining why it avoids the network workaround
- Update Deploy section: pull pre-built images instead of building locally

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use lookup-index named volume instead of docker/data/index bind mount
  (keeps podman and docker index data separate; fixes write.lock permission error)
- Point config volume to podman/serviceConfigs/lookup/ instead of docker/
- Add lookup-index.volume quadlet unit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add gitignore negation rules so .env.example and oep.env.example are
  tracked while .env files remain ignored (.env* glob was too broad)
- Add podman/.env.example to version control
- Document all required environment variables in README with descriptions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Serve the rootless quadlet deployment over HTTPS without exposing the
container directly:

- Add host nginx reverse proxy config (podman/nginx/oeplatform.conf) that
  terminates TLS on :443 and proxies to the container on 127.0.0.1:8080.
  No certbot — operator supplies their own certificate.
- Bind oep-oeplatform PublishPort to 127.0.0.1:8080 so the app is only
  reachable through nginx.
- Make Django HTTPS/host settings env-driven: OEP_DEBUG, OEP_URL,
  OEP_ALLOWED_HOSTS, OEP_BEHIND_TLS_PROXY (sets SECURE_PROXY_SSL_HEADER and
  secure cookies), OEP_CSRF_TRUSTED_ORIGINS. Defaults preserve dev behavior.
- Document the HTTPS setup and new env vars in the quadlets README and
  oep.env.example.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The production server sits behind an upstream reverse proxy / load balancer
that terminates HTTPS, so the host nginx no longer manages certificates:

- Drop the SSL server block and cert paths; nginx now listens on plain HTTP
  and proxies to the container on 127.0.0.1:8080.
- Preserve the upstream's X-Forwarded-Proto via a map so Django still sees
  the request as HTTPS, instead of clobbering it to $scheme (http).
- Update the quadlet comment and README HTTPS section accordingly (remove
  certbot/self-signed steps).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…age #2319

ontology/apps.py OntologyConfig.ready() aborts Django startup (raising
ImportError) when the OEO release folder or oeo_ext.owl are absent. Because
collectstatic/compress run django.setup() at build time, the image build
failed unless these artifacts existed beforehand.

Download build-files.zip (→ ontologies/oeo/<version>/…) and seed oeo_ext.owl
from the template *before* the collectstatic step. Use unzip -o / cp -f so a
stale copy pulled in by COPY . /app doesn't trigger an interactive overwrite
prompt that hangs the non-interactive build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…2319

The postgresql.jar driver is baked into the ontop image from
docker/serviceConfigs/ontop/ (docker/Dockerfile.ontop copies it to
/opt/ontop/lib/); it is NOT read from podman/serviceConfigs/ontop/, which is
only the runtime config mount. Previous docs implied the jar belonged in the
podman config dir, causing confusion.

- Rewrite podman/serviceConfigs/ontop/README.md: split into runtime files
  (ontology.owl, ontop.properties) vs the build-time driver in docker/.
- Drop postgresql.jar from podman/serviceConfigs/ontop/.gitignore (not used
  there).
- Broaden the root .gitignore rule to **/serviceConfigs/ontop/postgresql.jar
  so the driver stays out of the repo in any location.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#2319

COPY . /app was pulling local, untracked ontologies/ and media/ trees into the
image, which bloated the context and collided with the OEO release the
Dockerfile bakes (forcing the unzip -o workaround). Ignore them so the baked
artifacts are the single source of truth and the build context stays small.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The upstream LB terminates TLS and forwards cleartext HTTP to this host on
port 443 (the only port open to the LB), not 80. Switch the nginx listen
directive from 80 to 443 — still without ssl / no certificate, since TLS is
handled upstream. nginx runs as root so binding privileged :443 in front of
the rootless container (127.0.0.1:8080) is fine.

Update the quadlets README HTTPS section to match (listen 443, why the root
nginx shim is needed for a rootless container).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…2319

- Multi-stage Dockerfile.ontop fetches the PostgreSQL JDBC driver from Maven
  Central at build time (--build-arg JDBC_VERSION); no jar in the context.
- Bake the ontology and an empty default mapping so the endpoint starts with
  zero host files, regardless of database state.
- CI passes JDBC_VERSION as a build arg.
…ties #2319

- Supply the DB connection through ONTOP_DB_URL / ONTOP_DB_USER /
  ONTOP_DB_PASSWORD (read natively by ontop); remove ontop.properties.template.
- Fix the no-op ONTOP_OWL_FILE -> ONTOP_ONTOLOGY_FILE.
- Drop the ontop host bind mount and /opt/oeplatform path dependency; wait for
  postgres via ONTOP_WAIT_FOR.
- Mirror the same env-based config in podman-compose and docker-compose.dev.
- Add the ONTOP_DB_* block to the env examples.
Update the quadlets README, ontop service README and the ontop install guide
to reflect the baked artifacts, env-based DB connection and the empty default
mapping.
…2319

The lookup service bind-mounts its config from the repo. Instead of requiring a
manual /opt/oeplatform symlink, the unit ships an @@OEP_REPO@@ placeholder that
install.sh replaces with the checkout's absolute path at install time. Works
from any location with no manual steps — keeps the setup deterministic.
…oxy #2319

Script the nginx host setup so there are no manual steps: install-nginx.sh
installs nginx, writes the site config with server_name taken from OEP_URL in
oep.env (single source of truth), enables the site, drops the default one and
reloads. Idempotent and re-runnable; domain can also be passed as an argument.
Update the quadlets README HTTPS section to use it.
…2319

Support the two-account setup (rootless container user + separate sudo admin):
use sudo only when not already root, and look for oep.env under the invoking
user's home ($SUDO_USER) as well. Lets the admin run it as
`sudo bash install-nginx.sh <domain>` while the container user owns oep.env.
Django 4+ rejects CSRF_TRUSTED_ORIGINS entries without a scheme, which crashed
startup when OEP_CSRF_TRUSTED_ORIGINS held a bare host. Prepend https:// to any
entry lacking a scheme. Only affects deployments that set the var (podman);
docker setups don't set it, so the gated block is skipped unchanged.
…oxy #2319

The upstream Apache reverse proxy re-encrypts to the backend (opens an HTTPS
connection to this host on :443), so serving plain HTTP there fails with
"Error during SSL Handshake with remote server". Switch nginx back to
`listen 443 ssl` and have install-nginx.sh auto-generate a self-signed cert
(SANs for the public + internal FQDN) when none is provided; Apache's mod_proxy
does not verify the backend cert by default. Update the README HTTPS section.
Add a canonical, generic mkdocs section under
docs/installation/production-podman/ documenting the rootless
Podman + systemd Quadlets production deployment. Five sequenced
pages, each linking to the next:

- Overview & architecture (double-TLS proxy chain, services incl.
  the DBpedia Lookup service, oep network, named volumes; mermaid
  topology + request/TLS-flow diagrams)
- Install (install.sh, oep.env, image build, lookup config +
  indexers, enable services, nginx HTTPS, linger)
- Ontop (self-provisioning + repeatable mapping-apply procedure)
- Update (current local-build flow; forthcoming podman pull)
- Maintenance & troubleshooting (ops + genericised failure modes)

Placeholders only; no real hosts, credentials or server paths.
Wire the section into the mkdocs nav under Getting Started, and
enable mermaid diagrams + content tabs via pymdownx.superfences
custom_fences and pymdownx.tabbed.
The quadlet .container units reference the ghcr.io image names
(oeplatform-production, oeplatform-ontop) with the default pull
policy 'missing', so a locally-built image must carry that exact
name or the service fails with exit code 125. Update the Install
and Update pages to build under the ghcr names directly (no
localhost tag, no separate podman tag step), and add a warning
cross-linking the exit-125 troubleshooting entry.
jh-RLI added 2 commits July 22, 2026 23:38
In the forthcoming podman pull update flow the published image is
self-contained, so no git pull is needed to obtain a new image;
git pull is only required when the in-repo deployment config
(quadlets, install.sh, oep.env.example, nginx/lookup configs)
changed. The local-build flow still requires git pull because the
image is built from the checkout.
Make the new Production deployment (Podman) guide canonical and
tidy the surrounding docs:

- Trim podman/README.md and podman/quadlets/README.md to a
  quickstart that points at the guide (kills the prior duplication);
  fix their build commands to use the ghcr image names.
- Relabel the docker / compose docs as development/CI only
  (docker/README.md, installation-docker-dev.md) with a pointer to
  the production guide.
- Retire the orphan setup-ontop.md into a stub that folds into the
  guide's canonical Ontop page.
- Fix stale/broken content: the 'download JDBC manually' text +
  setuo-ontop.md typo link in installation-docker-dev.md, and the
  dead install-and-documentation link in docker/README.md.
- Reconcile the Postgres version to 14 (the version the
  oeplatform-postgres image ships) in architecture/infrastructure.md.
- Finish the cut-off context/docker.md sentence and the MOSS stub
  in architecture/infrastructure.md.
- Add SPDX/REUSE headers to the flagged docs pages (setup-ontop,
  installation, installation-docker-dev, context/docker,
  serviceConfigs/ontop/README).
- Remove the stray mapping.obda.bak-20260612.

reuse lint not run (CLI not installed here) — run in CI/a dev env.
@jh-RLI
jh-RLI marked this pull request as ready for review July 23, 2026 12:45
@jh-RLI
jh-RLI merged commit 42faedb into develop Jul 23, 2026
5 checks passed
@jh-RLI
jh-RLI deleted the feature-2319-add-podman-setup branch July 23, 2026 12:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Podman based deployment solution

1 participant