Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!-- SPECKIT START -->
For additional context about technologies to be used, project structure,
shell commands, and edge-case handling details, read specs/002-multiarch-image-cache/plan.md
shell commands, and edge-case handling details, read specs/003-support-adduser-utils/plan.md
and treat it as the authoritative implementation context.
<!-- SPECKIT END -->

Expand All @@ -16,6 +16,14 @@ The image must include the `jemalloc` implementation of `malloc` for improved me
To support development and testing, the resulting container will be multi-architecture, supporting both
`linux/amd64` and `linux/arm64` architectures.

This container image is intended to be used in turn as a base image for rails and other applications,
so the image must support the ability to install additional packages whose identities are not yet known
at this time.

The image must include the `jemalloc` implementation of `malloc` for improved memory performance.
To support development and testing, the resulting container will be multi-architecture, supporting both
`linux/amd64` and `linux/arm64` architectures.

The project will use `dependabot` to keep the pinned versions of Ruby, OpenSSL, and jemalloc up to
date.

Expand Down Expand Up @@ -47,11 +55,20 @@ The `Dockerfile` will be formatted with "here-doc" `RUN` blocks for clarity and
* Pull requests will be used for all changes, with code review and automated testing before merging.
* Use `dependabot` to keep dependencies up to date.

## Release Management

* When a new tag is pushed to the repository, the CI workflow will automatically build and push the
corresponding Docker image to the registry.
* The release process will include validation steps to ensure the image is built correctly and functions
as expected before it is published.
* The project should contain a changelog file that is updated with each release, detailing the changes
made in that release.

## Validation

* The project will use the `pre-commit` toolchain for local development to ensure code quality
and consistency.
* A `make lint` target will be used to lint all markdown files in the repository, ensuring
documentation quality.
* A `make lint` target will be used to lint all markdown, JSON, and YAML files in the repository,
ensuring source code quality.

<!-- project-specific instructions end -->
2 changes: 1 addition & 1 deletion .specify/feature.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"feature_directory": "specs/002-multiarch-image-cache"
"feature_directory": "specs/003-support-adduser-utils"
}
6 changes: 5 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
"true": true,
"git worktree": true,
"pre-commit": true,
"docker build": true
"docker build": true,
".specify/extensions/git/scripts/bash/create-new-feature.sh": true,
"adduser": true,
"cp": true,
"get_terminal_output": true
}
}
84 changes: 41 additions & 43 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -92,57 +92,55 @@ make install
rm -rf /tmp/build/ruby-*
EOF

# Assemble a minimal runtime filesystem for the final scratch image.
RUN <<'EOF'
set -eux

mkdir -p /runtime-root

# Copy Ruby, OpenSSL, and CA certificates required at runtime.
mkdir -p /runtime-root/usr /runtime-root/opt /runtime-root/etc/ssl
cp -a /usr/local /runtime-root/usr/local
cp -a /opt/openssl /runtime-root/opt/openssl
cp -a /etc/ssl/certs /runtime-root/etc/ssl/certs

# Copy dynamic libraries required by Ruby + linked shared libraries.
tmp_lib_list="$(mktemp)"
ldd /usr/local/bin/ruby | awk '{if ($1 ~ /^\//) print $1; else if ($3 ~ /^\//) print $3}' >> "$tmp_lib_list"
ldd /usr/local/lib/libruby.so.2.6 | awk '{if ($1 ~ /^\//) print $1; else if ($3 ~ /^\//) print $3}' >> "$tmp_lib_list"
ldd /usr/local/lib/libjemalloc.so.2 | awk '{if ($1 ~ /^\//) print $1; else if ($3 ~ /^\//) print $3}' >> "$tmp_lib_list"
ldd /opt/openssl/lib/libssl.so.1.1 | awk '{if ($1 ~ /^\//) print $1; else if ($3 ~ /^\//) print $3}' >> "$tmp_lib_list"
ldd /opt/openssl/lib/libcrypto.so.1.1 | awk '{if ($1 ~ /^\//) print $1; else if ($3 ~ /^\//) print $3}' >> "$tmp_lib_list"

sort -u "$tmp_lib_list" | while read -r lib; do
[ -n "$lib" ]
[ -e "$lib" ]
resolved="$(readlink -f "$lib")"

mkdir -p "/runtime-root$(dirname "$resolved")"
cp -a "$resolved" "/runtime-root$resolved"

if [ "$lib" != "$resolved" ]; then
mkdir -p "/runtime-root$(dirname "$lib")"
ln -sf "$resolved" "/runtime-root$lib"
fi
done

rm -f "$tmp_lib_list"
EOF

# ============================================================
# Stage 2: Final – minimal runtime image
# Stage 2: Final – small runtime image with debian base
# ============================================================
FROM busybox:1-musl
FROM debian:bookworm-slim

LABEL org.opencontainers.image.description="Ruby 2.6 image with jemalloc 5.3.1" \
org.opencontainers.image.source="https://github.com/UMNLibraries/ruby2.6-jemalloc-docker"

ARG RUBY_VERSION=2.6.10

ENV LD_LIBRARY_PATH=/opt/openssl/lib:/usr/local/lib \
RUBY_VERSION=${RUBY_VERSION}
ENV RUBY_VERSION=${RUBY_VERSION}

# Copy compiled Ruby, OpenSSL, and CA certificates from builder.
COPY --from=builder /usr/local /usr/local
COPY --from=builder /opt/openssl /opt/openssl
COPY --from=builder /etc/ssl/certs /etc/ssl/certs

# Copy curated runtime filesystem from builder.
COPY --from=builder /runtime-root/ /
# Install runtime library dependencies and register compiled library paths.
# Remove unnecessary build artifacts (headers, static libs, pkgconfig) to minimize image size.
RUN <<'EOF'
set -eux

# Step: install runtime dependencies required by Ruby, OpenSSL, jemalloc
apt-get update
apt-get install -y --no-install-recommends \
libffi8 \
libgdbm6 \
libncurses6 \
libreadline8 \
libyaml-0-2 \
zlib1g \
ca-certificates

# Step: remove build artifacts from copied Ruby and OpenSSL
rm -rf /usr/local/include \
/usr/local/lib/pkgconfig \
/usr/local/lib/*.a \
/usr/local/share/doc \
/usr/local/share/man \
/opt/openssl/include \
/opt/openssl/lib/pkgconfig \
/opt/openssl/lib/*.a

# Step: register compiled library paths for Ruby and OpenSSL at runtime
ldconfig /opt/openssl/lib /usr/local/lib

# Step: clean apt caches and temporary files
rm -rf /var/lib/apt/lists/* /var/tmp/* /tmp/*

EOF

CMD ["irb"]
44 changes: 43 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This repository provides a multistage Docker build that compiles:
- **jemalloc 5.3.0** – linked into Ruby at compile time via `--with-jemalloc` for improved memory performance.
- **Ruby 2.6.10** – the latest 2.6.x release, compiled from source against the above libraries.

The multistage build keeps the final image lean by carrying over only the compiled binaries and required runtime libraries into a `scratch`-based final image.
The multistage build carries compiled binaries and required runtime libraries into a `debian:bookworm-slim` final image, providing a practical balance between image size and runtime extensibility.

## Build Command Style

Expand Down Expand Up @@ -91,6 +91,48 @@ The verification script checks:
- Ruby reports `2.6.x`
- jemalloc is mapped into the running Ruby process (`/proc/self/maps`)

## Extending the Image: User Management

The `debian:bookworm-slim` final stage includes user-management utilities, enabling downstream images to create application users and groups. This is useful for running Ruby applications with reduced privileges.

### Example: Creating a Non-Root User

```dockerfile
FROM ghcr.io/umnlibraries/ruby2.6-jemalloc-docker:latest

# Create a non-root user for the application
RUN adduser --system --disabled-password --disabled-login \
--gecos "Ruby App" rubyapp

# Copy application code
COPY app/ /app/

# Set ownership and permissions
RUN chown -R rubyapp:rubyapp /app && chmod 750 /app

USER rubyapp
WORKDIR /app

ENTRYPOINT ["ruby", "app.rb"]
```

### Example: Managing Groups

```dockerfile
FROM ghcr.io/umnlibraries/ruby2.6-jemalloc-docker:latest

# Create a group and user
RUN groupadd webservices && \
adduser --system --disabled-password --ingroup webservices www-user

COPY app/ /app/
RUN chown -R www-user:webservices /app

USER www-user
```

The base image does not include verification of specific user-management commands in derived layers; downstream maintainers are responsible for validating user creation and permission workflows in their own Dockerfiles.

## CI/CD

GitHub Actions publishes one multi-platform image tag on pushes to `main` and manual release
Expand Down
40 changes: 40 additions & 0 deletions specs/003-support-adduser-utils/checklists/requirements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Specification Quality Checklist: Small Runtime Image with User-Management Utilities

**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-06-11
**Feature**: [spec.md](../spec.md)

## Content Quality

- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed

## Requirement Completeness

- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified

## Feature Readiness

- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification

## Notes

- All 16 checklist items now pass.
- **Approved Exception**: The spec includes `debian:bookworm-slim` as an explicit final-runtime base (FR-008) because:
- This constraint is a direct outcome of user clarifications and is foundational to the feature.
- It is tied to multiple functional requirements (FR-001, FR-003, FR-008, FR-009) and measurable outcomes (SC-001, SC-002, SC-005).
- It aligns with the constitution's requirement for deterministic build inputs (Principle III) and reproducible container behavior (Principle I).
- It is a requirement, not an implementation detail—reviewers must verify it as part of acceptance.
- The exception is documented in the spec's Requirements section under "Container & Runtime Constraints" to make it explicit and traceable.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Contract: Runtime Build and Verification Behavior

## Purpose

Define required externally observable behavior for image build, downstream utility support, and runtime verification.

## Interfaces

### 1. Image build interface

Expected behavior:
- Local build command (`docker build` or equivalent make target) succeeds.
- CI build publishes multi-architecture image variants (`linux/amd64`, `linux/arm64`).

### 2. Downstream user-management interface

Expected behavior:
- Final runtime base remains `debian:bookworm-slim`, preserving practical downstream extension workflows.
- Feature validation does not require dedicated command-presence checks for specific user-management binaries.

### 3. Runtime verification interface

Expected behavior:
- Runtime checks verify Ruby reports `2.6.x`.
- Runtime checks verify jemalloc is active.
- Verification runs for both target architectures.

## Invariants

- Final runtime base remains `debian:bookworm-slim` for this feature.
- Temporary package/build files are removed from final image layers where practical.
- README/operational docs are updated when behavior changes.

## Failure Conditions

- Ruby or jemalloc verification fails on either architecture.
- Hygiene check indicates temporary file cleanup regressions.
48 changes: 48 additions & 0 deletions specs/003-support-adduser-utils/data-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Data Model: Runtime Utility Baseline Feature

## Entity: Runtime Utility Baseline

**Purpose**: Represents runtime capabilities available by default in the final `debian:bookworm-slim` image.

### Fields

- `base_image`: final runtime base image reference
- `user_mgmt_available`: whether downstream `adduser` workflows function in derived images
- `runtime_packages`: runtime package subset retained in final image
- `cleanup_state`: confirmation that temp/cache files are removed

### Validation Rules

- `base_image` must be `debian:bookworm-slim`.
- `user_mgmt_available` must be true for release eligibility.
- `cleanup_state` must pass hygiene checks.

## Entity: Derived Image Build Step

**Purpose**: Captures a downstream Dockerfile step that uses user-management commands.

### Fields

- `command`: user-management command executed by downstream image
- `mode`: non-interactive or interactive
- `result`: success/failure

### Validation Rules

- Non-interactive downstream build usage must succeed.

## Entity: Verification Result

**Purpose**: Captures build and runtime verification outcomes for each architecture.

### Fields

- `platform`: `linux/amd64` or `linux/arm64`
- `build_passed`: build success flag
- `ruby_version_ok`: Ruby 2.6 verification result
- `jemalloc_ok`: allocator verification result
- `hygiene_ok`: temp-file cleanup verification result

### Validation Rules

- All verification fields must pass for both target platforms before release.
Loading
Loading