🔧 containers: Replace Docker with Podman and UBI-based Containerfile
Some checks failed
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / lint (pull_request) Has been cancelled
CI / test (pull_request) Has been cancelled

Replace the broken Docker setup (Python 3.6 Alpine, `pipenv`, hardcoded
credentials) with a modern Podman-based development environment using
Red Hat Universal Base Images for OpenShift compatibility.

`Containerfile` (replaces `Dockerfile`):
- Multi-stage build: builder stage installs Poetry and dependencies,
  runtime stage copies only the installed packages for a smaller image
- Base image: UBI 9 Python 3.12 (OpenShift-certified)
- Runs as non-root `USER 1001` (required by OpenShift SCC)
- Gunicorn as the application server instead of Django's runserver
- No longer calls `generate_client_secrets.sh` during build

`podman-compose.yml` (replaces `docker-compose.yml`):
- PostgreSQL 17, Redis 7, RabbitMQ 3 with health checks
- `depends_on` with `service_healthy` conditions instead of links
- Named volume (`pgdata`) for PostgreSQL persistence
- SELinux `:z` labels on bind mounts for Fedora hosts
- Gunicorn with `--reload` for development hot-reload

Also adds:
- `.env.example` — template for local development secrets
- `.containerignore` — excludes .git, secrets, and build artifacts
  from the container image

Closes #222.
Closes #265.

Assisted-by: Claude Opus 4.6 (1M context)
Signed-off-by: Justin Wheeler <jwheel@fedoraproject.org>
This commit is contained in:
Justin Wheeler 2026-03-29 02:26:18 -04:00
commit 565f7757bf
Signed by: jflory7
GPG key ID: 6BD803B36BF8F62E
7 changed files with 128 additions and 75 deletions

15
.containerignore Normal file
View file

@ -0,0 +1,15 @@
.git
.forgejo
.claude
__pycache__
*.pyc
*.pyo
.env
*.sqlite3
htmlcov/
.coverage
docs/_build/
static/
media/
selenium-screenshots/
whoosh_index/

View file

@ -1,2 +0,0 @@
**/*.pyc
**/__pycache__

16
.env.example Normal file
View file

@ -0,0 +1,16 @@
# Copy this file to .env and fill in the values.
# Used by podman-compose for local development.
# OIDC credentials (register at Fedora OIDC provider)
OIDC_RP_CLIENT_ID=
OIDC_RP_CLIENT_SECRET=
# FAS admin account for username lookups
ADMIN_USERNAME=
ADMIN_PASSWORD=
# Django secret key (generate with: python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())")
SECRET_KEY=only-for-development
# Redis URL for cache (Celery uses its own config in settings)
REDIS_URL=redis://redis:6379/1

30
Containerfile Normal file
View file

@ -0,0 +1,30 @@
# Stage 1: Install dependencies in a temporary builder image.
# This stage pulls in Poetry, compilers, and build tools that are
# needed to install Python packages but not needed at runtime.
FROM registry.access.redhat.com/ubi9/python-312:latest AS builder
WORKDIR /app
COPY pyproject.toml poetry.lock* ./
RUN pip install --no-cache-dir poetry \
&& poetry config virtualenvs.create false \
&& poetry install --without dev,docs --no-interaction --no-ansi
# Stage 2: Create the final runtime image.
# Starts from a clean base and copies only the installed packages
# from the builder, producing a smaller image without build tools.
FROM registry.access.redhat.com/ubi9/python-312:latest
WORKDIR /app
COPY --from=builder /opt/app-root /opt/app-root
COPY . /app
RUN python manage.py collectstatic --noinput 2>/dev/null || true
# Run as non-root user (required by OpenShift security context constraints)
USER 1001
EXPOSE 8000
CMD ["gunicorn", "happinesspackets.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "3"]

View file

@ -1,33 +0,0 @@
# Issue with celery=4.2.1 with Py3.7 hence Py3.6.8 used
FROM python:3.6.8-alpine
ENV DJANGO_SETTINGS_MODULE=happinesspackets.settings.dev
ENV FEDORA_MESSAGING_CONF=/app/config.toml
# Set current working directory
WORKDIR /app
# Install required system packages
RUN apk add --update --no-cache \
build-base \
bash \
curl \
readline \
libffi-dev \
ncurses-dev \
python3-dev \
postgresql-dev
# Copy project files into container
COPY . /app
# Check if client_secrets.json is present, and generate if not
RUN ./generate_client_secrets.sh
# Install required Python packages
RUN pip install pipenv \
&& pipenv install --system --deploy --dev
# Expose Django port
EXPOSE 8000
CMD [ "./manage.py", "collectstatic", "--noinput" ]

View file

@ -1,40 +0,0 @@
version: "3"
services:
web:
build: .
restart: always
command: bash -c "python manage.py migrate --noinput && python manage.py rebuild_index --noinput && python manage.py runserver 0.0.0.0:8000"
volumes:
- ./templates:/app/templates:z
- ./assets:/app/assets:z
- ./happinesspackets:/app/happinesspackets:z
ports:
- "8000:8000"
links:
- db
- redis
- rabbitmq
rabbitmq:
image: rabbitmq:alpine
ports:
- "5672:5672"
environment:
RABBITMQ_DEFAULT_PASS: pass
RABBITMQ_DEFAULT_USER: user
RABBITMQ_DEFAULT_VHOST: vhost
redis:
image: redis:alpine
ports:
- "6379:6379"
celery:
build: .
command: celery worker -A happinesspackets -l info
links:
- db
- redis
# ports:
# - "587:587"
db:
image: postgres:10-alpine
environment:
POSTGRES_PASSWORD: example

67
podman-compose.yml Normal file
View file

@ -0,0 +1,67 @@
version: "3"
services:
web:
build:
context: .
containerfile: Containerfile
environment:
DJANGO_SETTINGS_MODULE: happinesspackets.settings.dev
FEDORA_MESSAGING_CONF: /app/config.toml
ports:
- "8000:8000"
volumes:
- ./happinesspackets:/app/happinesspackets:z
- ./templates:/app/templates:z
- ./assets:/app/assets:z
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
command: >
bash -c "python manage.py migrate --noinput &&
gunicorn happinesspackets.wsgi:application --bind 0.0.0.0:8000 --reload"
db:
image: docker.io/postgres:17-alpine
environment:
POSTGRES_PASSWORD: example
POSTGRES_DB: postgres
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "postgres"]
interval: 5s
retries: 5
redis:
image: docker.io/redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
retries: 5
celery:
build:
context: .
containerfile: Containerfile
environment:
DJANGO_SETTINGS_MODULE: happinesspackets.settings.dev
command: celery -A happinesspackets worker -l info
depends_on:
- db
- redis
rabbitmq:
image: docker.io/rabbitmq:3-management-alpine
environment:
RABBITMQ_DEFAULT_USER: user
RABBITMQ_DEFAULT_PASS: pass
RABBITMQ_DEFAULT_VHOST: vhost
healthcheck:
test: ["CMD", "rabbitmqctl", "status"]
interval: 10s
retries: 5
volumes:
pgdata: