diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..59470ec --- /dev/null +++ b/.dockerignore @@ -0,0 +1,20 @@ +# .git is deliberately not ignored: jekyll-last-modified-at needs the history + +_site +_pages +.jekyll-cache +.jekyll-metadata +vendor +.bundle +.netlify + +Dockerfile +compose.yaml +.env +.dockerignore +.github +.gitignore +.gitattributes + +*.sw? +*~ diff --git a/.gitignore b/.gitignore index 30a73b8..dc042ae 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ vendor/ # Local Netlify folder .netlify + +# Host uid/gid for docker compose, see the Makefile +.env diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..cfa6365 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,68 @@ +# syntax=docker/dockerfile:1 + +# Development image for the docs site. +# +# Gemfile.lock only lists x86_64-linux, so build on amd64 or run +# `bundle lock --add-platform aarch64-linux` first. Not Alpine: the locked +# sass-embedded, ffi and google-protobuf gems are glibc builds. + +ARG RUBY_VERSION=3.4 +ARG DEBIAN_RELEASE=trixie + +FROM ruby:${RUBY_VERSION}-slim-${DEBIAN_RELEASE} AS base + +ENV LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 \ + TZ=UTC \ + BUNDLE_PATH=/usr/local/bundle \ + BUNDLE_JOBS=4 \ + BUNDLE_RETRY=3 + +# jekyll-last-modified-at reads each file's last commit date +RUN apt-get update \ + && apt-get install -y --no-install-recommends git \ + && rm -rf /var/lib/apt/lists/* + + +FROM base AS gems + +# eventmachine and http_parser.rb have no precompiled gems +RUN apt-get update \ + && apt-get install -y --no-install-recommends build-essential libssl-dev \ + && rm -rf /var/lib/apt/lists/* + +ENV BUNDLE_FROZEN=true + +WORKDIR /app +COPY Gemfile Gemfile.lock ./ +RUN bundle install && rm -rf "${BUNDLE_PATH}"/cache + + +FROM base AS dev + +# Pass --build-arg UID=$(id -u) so generated files match the host owner +ARG UID=1000 +ARG GID=1000 + +RUN if ! getent group "${GID}" >/dev/null; then groupadd --gid "${GID}" jekyll; fi \ + && useradd --uid "${UID}" --gid "${GID}" --create-home --shell /bin/bash jekyll + +# git refuses to read a repo it thinks someone else owns +RUN git config --system --add safe.directory /app + +COPY --from=gems --chown=${UID}:${GID} ${BUNDLE_PATH} ${BUNDLE_PATH} + +# WORKDIR would create these root-owned, and jekyll writes to all three +RUN install -d -o "${UID}" -g "${GID}" /app /app/_site /app/.jekyll-cache + +WORKDIR /app +COPY --chown=${UID}:${GID} . . + +USER jekyll + +# 4000 site, 35729 livereload +EXPOSE 4000 35729 + +# Also allows `docker run jekyll build` +ENTRYPOINT ["bundle", "exec"] +CMD ["jekyll", "serve", "--host", "0.0.0.0", "--livereload", "--drafts"] diff --git a/Makefile b/Makefile index 1b5caf8..322491f 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ # Makefile for BleachBit documentation site -.PHONY: help clean serve build install update +.PHONY: help clean serve build install update \ + docker-serve docker-build docker-shell docker-down # Default target help: @@ -11,6 +12,11 @@ help: @echo " build - Build the static site" @echo " install - Install Ruby dependencies" @echo " update - Update Ruby dependencies" + @echo "" + @echo " docker-serve - Start the development server in a container" + @echo " docker-build - Build the static site in a container" + @echo " docker-shell - Open a shell in the container" + @echo " docker-down - Stop the container" # Clean generated files and dependencies clean: @@ -49,3 +55,26 @@ serve: serve-prod: @echo "Starting production-like server..." bundle exec jekyll serve + +# Compose reads .env automatically, so the container runs as the host user +# instead of root. Delete this file if your uid changes. +.env: + @printf 'HOST_UID=%s\nHOST_GID=%s\n' $$(id -u) $$(id -g) > $@ + @echo "Wrote $@" + +# Serve in a container +docker-serve: .env + docker compose up --build + +# Build the static site in a container +docker-build: .env + docker compose run --rm --build docs jekyll build + @echo "Site built in _site/" + +# Shell in the container, for bundle update and friends +docker-shell: .env + docker compose run --rm --build --entrypoint bash docs + +# Stop the container +docker-down: + docker compose down diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..0b4e1fe --- /dev/null +++ b/compose.yaml @@ -0,0 +1,33 @@ +services: + docs: + build: + context: . + target: dev + args: + UID: ${HOST_UID:-1000} + GID: ${HOST_GID:-1000} + environment: + JEKYLL_ENV: development + ports: + - "127.0.0.1:4000:4000" + - "127.0.0.1:35729:35729" + volumes: + # Append :z under podman or a docker daemon with selinux-enabled. That + # relabels this directory to container_file_t on the host. + - .:/app + # Inherits `jekyll serve` from the image. Docker Desktop on macOS and + # Windows needs inotify replaced with polling: + # command: [jekyll, serve, --host, 0.0.0.0, --livereload, --drafts, --force_polling] + healthcheck: + test: + [ + "CMD", + "ruby", + "-rnet/http", + "-e", + "exit Net::HTTP.get_response(URI('http://127.0.0.1:4000/')).is_a?(Net::HTTPSuccess) ? 0 : 1", + ] + interval: 30s + timeout: 5s + start_period: 60s + retries: 3