Skip to content
Open
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
19 changes: 19 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Ignore files that are not needed for building the base image
.git
.gitignore
README.md
*.md
LICENSE
examples/
.github/
Dockerfile*
.dockerignore

# Common unnecessary files
**/.DS_Store
**/*~
**/*.log
tmp/
log/
vendor/bundle/ # if present in context
node_modules/
31 changes: 31 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Build

on:
pull_request:
push:
branches: [main, master]
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build canis-base
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
platforms: linux/amd64
push: false
tags: canis-base:ci
cache-from: type=gha
cache-to: type=gha,mode=max
56 changes: 56 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Release

on:
release:
types: [published]
workflow_dispatch:
inputs:
version:
description: 'Semantic version'
required: true

jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Determine version
id: version
run: |
if [ "${{ github.event_name }}" = "release" ]; then
VERSION="${{ github.ref_name }}"
else
VERSION="${{ github.event.inputs.version }}"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "sha=${{ github.sha }}" >> $GITHUB_OUTPUT

- name: Build and push canis-base
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
platforms: linux/amd64
push: true
tags: |
ghcr.io/datacite/canis-base:latest
ghcr.io/datacite/canis-base:${{ steps.version.outputs.version }}
ghcr.io/datacite/canis-base:${{ steps.version.outputs.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
62 changes: 62 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# syntax=docker/dockerfile:1.7
#
# DataCite Canis Base Image
#
# Shared foundation for Canis Rails services.
# Service-specific packages (Percona, AWS CLI, ImageMagick, etc.) stay in app Dockerfiles.

FROM phusion/passenger-ruby40:3.1.6

LABEL maintainer="support@datacite.org" \
org.opencontainers.image.source="https://github.com/datacite/docker-canis-base" \
org.opencontainers.image.description="DataCite Canis Base (slim core)"

ENV HOME=/home/app \
LC_ALL=en_US.UTF-8 \
LANG=en_US.UTF-8

# ============================================================================
# Core system packages + common native gem build dependencies
# ============================================================================
RUN apt-get update && apt-get upgrade -y -o Dpkg::Options::="--force-confold" && \
apt-get install -y --no-install-recommends \
ntp wget ca-certificates tzdata shared-mime-info \
nano tmux \
build-essential libxslt1-dev libyaml-dev zlib1g-dev pkg-config \
&& apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# ============================================================================
# User permissions
# ============================================================================
RUN usermod -a -G docker_env app

# ============================================================================
# Ruby 4 + modern Bundler baseline
# ============================================================================
RUN bash -lc 'rvm --default use ruby-4.0.1 && gem install rubygems-update -v 3.5.6 && gem install bundler -v 2.6.9'

# ============================================================================
# Standard Phusion layout
# ============================================================================
RUN rm -f /etc/service/nginx/down && \
rm -f /etc/nginx/sites-enabled/default && \
mkdir -p /etc/nginx/templates /etc/service/shoryuken /etc/my_init.d /etc/nginx/conf.d

# Copy common configuration and scripts from the base repo's vendor/docker/.
# Apps can still override any of these by COPY-ing their own version later in their Dockerfile.
COPY vendor/docker/ntp.conf /etc/ntp.conf
COPY vendor/docker/00_app_env.conf /etc/nginx/conf.d/00_app_env.conf
COPY vendor/docker/10_ssh.sh /etc/my_init.d/10_ssh.sh
COPY vendor/docker/shoryuken.sh /etc/service/shoryuken/run

# Make sure init scripts are executable
RUN chmod +x /etc/my_init.d/10_ssh.sh \
/etc/service/shoryuken/run

# Enable SSH (root + PUBLIC_KEY via 10_ssh.sh)
RUN rm -f /etc/service/sshd/down && \
/etc/my_init.d/00_regen_ssh_host_keys.sh

WORKDIR /home/app/webapp
CMD ["/sbin/my_init"]
EXPOSE 80
99 changes: 96 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,98 @@
# docker-canis-base
# Canis Base

Shared Docker base for DataCite Canis services.
Shared Docker image for DataCite Canis Rails services (Lupo, Levriero, Events, Volpino, Sashimi).

Design and initial implementation are under review in a PR.
### Image

| Image | Purpose |
|-------|---------|
| `canis-base` | Shared Phusion/Passenger + Ruby foundation |

### Philosophy

- Keep the base to the **intersection** of shared needs across Canis services.
- Service-specific packages and scripts stay in each app's Dockerfile (e.g. Lupo's Percona Toolkit and AWS CLI).
- Match existing fleet contracts (SSH, Passenger env, NTP, Shoryuken guards) so adoption is a thin Dockerfile change.

## Quick start

### Build locally

```bash
docker buildx build \
--platform linux/amd64 \
--tag canis-base:local \
--load \
-f Dockerfile .
```

### Usage in application Dockerfiles

```dockerfile
FROM ghcr.io/datacite/canis-base:1.2.3
```

Pin to a full commit SHA for maximum reproducibility if needed.

Lupo and other services add only what they need after `FROM` (see `examples/`).

## What's in the image

- `phusion/passenger-ruby40` (Ubuntu 24.04)
- Ruby 4.0.1 + rubygems 3.5.6 + bundler 2.6.9
- Passenger + Nginx enabled; default site removed
- Common packages: ntp, wget, ca-certificates, tzdata, shared-mime-info, nano, tmux
- Common native gem build deps: build-essential, libxslt1-dev, libyaml-dev, zlib1g-dev, pkg-config
- `app` in `docker_env` group
- SSH enabled; `PUBLIC_KEY` installed for **root** at startup
- Baked shared config/scripts (see below)
- Guarded Shoryuken runit service

**Not in the base** (add in the app when needed): Percona Toolkit, AWS CLI, MySQL client headers, ImageMagick/graphics libs, Chrome, dockerize, migrate-on-boot scripts.

## Baked `vendor/docker/` files

| Source | Destination | Behavior |
|--------|-------------|----------|
| `00_app_env.conf` | `/etc/nginx/conf.d/00_app_env.conf` | `passenger_app_env development;` (overridable via `PASSENGER_APP_ENV`) |
| `ntp.conf` | `/etc/ntp.conf` | Amazon NTP pool (fleet standard) |
| `10_ssh.sh` | `/etc/my_init.d/10_ssh.sh` | `PUBLIC_KEY` → `/root/.ssh/authorized_keys` |
| `shoryuken.sh` | `/etc/service/shoryuken/run` | Starts only if `AWS_REGION` is set and `DISABLE_QUEUE_WORKER` is unset |

**Migrations are not in the base.** Apps that migrate on boot should keep their own `90_migrate.sh` and `COPY` it into `/etc/my_init.d/`.

**Overrides:** `COPY` your own file after `FROM` to replace a baked path. Keep service-only files in the app (`webapp.conf`, metrics, nginx templates, precompile, etc.).

## Runtime env conventions

| Variable | Role |
|----------|------|
| `PUBLIC_KEY` | SSH public key for root |
| `PASSENGER_APP_ENV` | Overrides Passenger app env (default development via conf) |
| `AWS_REGION` | Required for Shoryuken to start |
| `DISABLE_QUEUE_WORKER` | If set (any non-empty value), Shoryuken does not start |
| `SERVER_ROLE` | Used by app-owned migrate scripts (not the base) |

## Tagging

Releases are driven by git tags. Tags typically include `latest`, the version tag, and the commit SHA. Prefer pinning apps to a version tag:

```dockerfile
FROM ghcr.io/datacite/canis-base:1.2.3
```

## Ruby version

This image supports **Ruby 4.x** only. Services still on Ruby 3 need to finish that upgrade before adopting this base.

## Examples

See `examples/`:

- `lupo.Dockerfile` — base + Lupo-only packages/scripts (Percona, AWS CLI, metrics, …)
- `light-service.Dockerfile` — base + app-only bits

## Maintenance

- Change `Dockerfile` when updating core packages, Ruby, or shared scripts.
- Keep Percona, AWS CLI, and other ops tools in the apps that need them (primarily Lupo).
1 change: 1 addition & 0 deletions VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.0.0
35 changes: 35 additions & 0 deletions examples/light-service.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# syntax=docker/dockerfile:1.7
#
# Example for a Canis service using only the slim core base
# (e.g. events, levriero, sashimi, volpino — after Ruby 4)
# Base already provides: passenger/nginx, app_env, ntp, root SSH, guarded shoryuken

FROM ghcr.io/datacite/canis-base:1.2.3

LABEL maintainer="support@datacite.org"

# Service-only packages (examples)
# RUN apt-get update && apt-get install -y --no-install-recommends \
# default-libmysqlclient-dev imagemagick && \
# apt-get clean && rm -rf /var/lib/apt/lists/*

COPY vendor/docker/webapp.conf /etc/nginx/sites-enabled/webapp.conf

# Optional: enable migrations on boot (kept in the app, not the base)
# COPY vendor/docker/90_migrate.sh /etc/my_init.d/90_migrate.sh

# Optional: override base shoryuken/ssh/app_env only if this service differs
# COPY vendor/docker/shoryuken.sh /etc/service/shoryuken/run

COPY . /home/app/webapp/
RUN mkdir -p tmp/pids tmp/storage && \
chown -R app:app /home/app/webapp && \
chmod -R 755 /home/app/webapp

WORKDIR /home/app/webapp
RUN mkdir -p vendor/bundle && \
chown -R app:app . && \
/sbin/setuser app bundle config set --local path 'vendor/bundle' && \
/sbin/setuser app bundle install

EXPOSE 80
63 changes: 63 additions & 0 deletions examples/lupo.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# syntax=docker/dockerfile:1.7
#
# Example Lupo Dockerfile using canis-base
# Base already provides: passenger/nginx, app_env, ntp, root SSH, guarded shoryuken
# Lupo-only ops (Percona, AWS CLI, metrics, templates) stay in this Dockerfile

FROM ghcr.io/datacite/canis-base:1.2.3

LABEL maintainer="support@datacite.org"

ENV NGINX_ENVSUBST_OUTPUT_DIR=/etc/nginx/sites-enabled

# Lupo-only packages
RUN apt-get update && \
apt-get install -y --no-install-recommends \
default-libmysqlclient-dev imagemagick gettext \
gnupg lsb-release unzip \
libdbd-mysql-perl libdbi-perl libterm-readkey-perl libio-socket-ssl-perl && \
apt-get clean && rm -rf /var/lib/apt/lists/*

ARG PERCONA_TOOLKIT_VERSION=3.7.1-3.noble

# Percona Toolkit (Lupo only)
RUN wget https://repo.percona.com/apt/percona-release_latest.generic_all.deb && \
apt-get update && \
apt-get install -y --no-install-recommends ./percona-release_latest.generic_all.deb && \
percona-release enable pt release && \
apt-get update && \
apt-get install -y --no-install-recommends percona-toolkit=${PERCONA_TOOLKIT_VERSION} && \
apt-mark hold percona-toolkit && \
rm -f percona-release_latest.generic_all.deb && \
apt-get clean && rm -rf /var/lib/apt/lists/*

# AWS CLI v2 (Lupo passenger metrics / scaling)
RUN wget -q https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip -O /tmp/awscliv2.zip && \
unzip -q /tmp/awscliv2.zip -d /tmp && \
/tmp/aws/install && \
rm -rf /tmp/awscliv2.zip /tmp/aws

# Lupo-specific nginx configuration
COPY vendor/docker/webapp.conf.template /etc/nginx/templates/webapp.conf.template
COPY vendor/docker/70_nginx_templates.sh /etc/my_init.d/70_nginx_templates.sh

# Passenger metrics
RUN mkdir -p /etc/service/passenger-metrics
COPY vendor/docker/passenger-metrics-run.sh /etc/service/passenger-metrics/run

# Migrations stay in the app (not in the base)
COPY vendor/docker/90_migrate.sh /etc/my_init.d/90_migrate.sh

# Application code
COPY . /home/app/webapp/
RUN mkdir -p tmp/pids tmp/storage && \
chown -R app:app /home/app/webapp && \
chmod -R 755 /home/app/webapp

WORKDIR /home/app/webapp
RUN mkdir -p vendor/bundle && \
chown -R app:app . && \
/sbin/setuser app bundle config set --local path 'vendor/bundle' && \
/sbin/setuser app bundle install

EXPOSE 80
2 changes: 2 additions & 0 deletions vendor/docker/00_app_env.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# File will be overwritten if user runs the container with `-e PASSENGER_APP_ENV=...`!
passenger_app_env development;
4 changes: 4 additions & 0 deletions vendor/docker/10_ssh.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
if [ "${PUBLIC_KEY}" ]; then
echo "${PUBLIC_KEY}" > /root/.ssh/authorized_keys
fi
4 changes: 4 additions & 0 deletions vendor/docker/ntp.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
server 0.amazon.pool.ntp.org iburst
server 1.amazon.pool.ntp.org iburst
server 2.amazon.pool.ntp.org iburst
server 3.amazon.pool.ntp.org iburst
7 changes: 7 additions & 0 deletions vendor/docker/shoryuken.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/sh
cd /home/app/webapp
exec 2>&1
# Start only in AWS; allow opt-out with DISABLE_QUEUE_WORKER (e.g. local compose)
if [ -n "$AWS_REGION" ] && [ -z "$DISABLE_QUEUE_WORKER" ]; then
exec /sbin/setuser app bundle exec shoryuken -R -C config/shoryuken.yml
fi
Loading