Files
clawlama/AI.md
T
jason ac0a99a041 📖 Standardize AI.md; move project spec to IDEA.md 📖
AI.md is now the standardized application-image specification from
claudemgr/docker/CASJAYSDEVDOCKER.md (PARTs 0–8: critical rules, repo
model, template system and OCI label canon, tooling, .env.scripts,
runtime system, README layout, CI/CD, verification gates). The former
AI.md — the full ClawLama project specification (OpenClaw + Ollama
single-container design) — moved wholesale to IDEA.md, which did not
previously exist, so nothing was lost.

- AI.md: replaced with the CASJAYSDEVDOCKER.md master template
- IDEA.md: new — carries the ClawLama project specification moved out of AI.md
2026-08-14 15:23:39 -04:00

28 KiB
Raw Blame History

CasjaysDev Docker Application Image Specification (casjaysdevdocker)

Name: {name}

About this file: This is the complete, authoritative specification for a CasjaysDev Docker application image repository (casjaysdevdocker/{name}). It is a master template — copied into an app image repo as that repo's AI.md. It is permanent — never delete it from a repo that carries it.

Note: {name} in this file is a reference token, not setup-time replacement text. Its value is always the repo directory basename (basename "$PWD").

Maintenance procedure: The bootstrap/update runbook (regenerating files after upstream template changes, creating new repos) is NOT in this file — it lives in the dockersrc-bootstrap agent (it handles both repo families via REPO_TYPE detection). This file defines the standards that procedure enforces.


PART INDEX

PART Title
0 Critical rules
1 Repository model & structure
2 Template system reference
3 Tooling — gen-dockerfile & gen-script
4 .env.scripts reference
5 Runtime system — setup scripts, entrypoint, init.d
6 README.md standard layout
7 CI/CD workflows
8 Verification & commit

PART 0: CRITICAL RULES

Org mapping

System Org Example
GitHub (source) casjaysdevdocker https://github.com/casjaysdevdocker/{name}
Docker Hub (push) casjaysdevdocker casjaysdevdocker/{name}

casjaysdevdocker repos are applications (gitea, opengist, super-productivity, ampache, aria2, …). They always build FROM the pre-built, multi-arch casjaysdev/* base images — never directly from upstream distro images. The bases themselves live in the separate dockersrc org (GitHub dockersrc/{base} → Docker Hub casjaysdev/{base}) — see the base specification (DOCKERSRC.md).

Non-negotiable rules

  1. AI.md is permanent — never delete it from the repo.
  2. Generated files are owned by the template system — never hand-tune content that gen-dockerfile regenerates (see PART 1 ownership table); fix the upstream gen-dockerfile template instead, then regenerate.
  3. Hand-crafted files are owned by the repogen-dockerfile must never overwrite app-specific init.d scripts, custom bin scripts, a 05-custom.sh with real content, or a hand-crafted README (PART 6).
  4. Removed OCI labels stay removed (PART 2) — never re-add base.name, schema-version, or duplicate authors/source entries.
  5. image.url is a browsable pagehttps://hub.docker.com/r/casjaysdevdocker/{name}. docker.io is only a registry pull host; it is never a label URL.
  6. image.source and image.documentation are the GitHub repohttps://github.com/casjaysdevdocker/{name}.
  7. One Dockerfile, one file set — app repos build one image (latest + date tag); version variants (Dockerfile.{ver}) belong to base repos only.
  8. Always FROM casjaysdev/<base> — never pull an upstream distro image directly; the base repos exist so every app shares one patched, multi-arch foundation.
  9. Only root/, tmp/, and usr/ may exist at rootfs/ top level (PART 1).
  10. Maintenance runs through the dockersrc-bootstrap agent — do not improvise the update procedure from memory.

PART 1: REPOSITORY MODEL & STRUCTURE

What an app image repo is

A casjaysdevdocker/{name} repo containerizes one application on top of a casjaysdev/* base. It publishes a single image (casjaysdevdocker/{name}:latest plus a date tag) — no per-version Dockerfile variants. The application itself is installed in 05-custom.sh and started by one or more init.d service scripts.

Standard tree

{name}/
├── AI.md                          # This specification (permanent)
├── Dockerfile                     # [generated] single build file
├── .dockerignore                  # [generated]
├── .env.scripts                   # [generated] build config
├── .gitattributes                 # [generated]
├── .gitea/workflows/
│   └── build.yml                  # [generated] gen-dockerfile actions
├── .gitignore                     # [generated]
├── LICENSE.md                     # License (WTFPL / app's own license)
├── README.md                      # [generated*] standard layout (PART 6)
└── rootfs/                        # Container filesystem overlay
    ├── root/docker/setup/         # [generated*] build-time setup scripts 0007
    ├── tmp/                       # staged files installed at build time (optional)
    └── usr/local/
        ├── bin/                   # [generated*] entrypoint.sh, pkmgr, symlink, copy,
        │                          #   healthcheck + [hand-crafted] app-specific scripts
        └── etc/docker/
            ├── env/               # [hand-crafted] build/runtime env fragments (optional)
            ├── functions/
            │   └── entrypoint.sh  # [generated] entrypoint function library
            └── init.d/            # [hand-crafted] runtime init scripts (one per service)

[generated] — safe to regenerate; local edits will be lost. [generated*] — regenerated from the template, EXCEPT files carrying repo-specific content (05-custom.sh with a real body, extra bin scripts, a hand-crafted README) — those follow the hand-crafted rules in PARTs 5 and 6. [hand-crafted] — never overwritten by the template system.

App repos may additionally carry project files (IDEA.md, CLAUDE.md, TODO.AI.md) per the global project conventions — they are repo-owned and never touched by tooling.

rootfs top-level policy

The only valid directories at the rootfs/ root are root/, tmp/, and usr/. Anything else is a leftover from old patterns. Migration map:

Old rootfs path Correct rootfs path
rootfs/etc/{path} rootfs/tmp/etc/{path}
rootfs/config/{path} rootfs/tmp/etc/{path}
rootfs/data/{path} rootfs/tmp/var/{path}
rootfs/var/{path} rootfs/tmp/var/{path}
rootfs/opt/{path} rootfs/tmp/opt/{path}
rootfs/share/{path} rootfs/usr/local/share/{path}

rootfs/usr/local/share/template-files/ is retired — the DEFAULT_TEMPLATE_DIR, DEFAULT_FILE_DIR, DEFAULT_DATA_DIR, and DEFAULT_CONF_DIR variables were removed from the template system; the entrypoint installs staged files from rootfs/tmp/etc/ at container start instead.

Repo type detection

A repo is an app repo when no Dockerfile.* variant files exist:

if find . -maxdepth 1 -name 'Dockerfile.*' -type f | grep -q -- .; then
  REPO_TYPE="base"
else
  REPO_TYPE="app"
fi

PART 2: TEMPLATE SYSTEM REFERENCE

Templates ship with gen-dockerfile, installed at /usr/local/share/CasjaysDev/scripts/templates/dockerfiles/ ($CASJAYSDEVDIR/templates/dockerfiles/ in a dev checkout). To inspect what the current templates produce, generate a fresh reference tree in a temp dir:

gen-dockerfile /tmp/gen-dockerfile/{org}/{repo} {distro}

See gen-dockerfile --help for supported distros/types. Keep this PART in sync whenever the templates change.

Template inventory

The template name selects the base OS family; for an app repo the resulting pull URL is always the matching casjaysdev/* image:

Template Final stage Init / PID 1 App pulls FROM
alpine.template scratch.template tini casjaysdev/alpine
debian.template scratch.template tini casjaysdev/debian
ubuntu.template scratch.template tini casjaysdev/ubuntu
rhel.template scratch.template tini casjaysdev/almalinux
archlinux.template scratch.template tini casjaysdev/archlinux (multi-arch manifest)
web.template systemd.template /sbin/init casjaysdev/web
xorg.template systemd.template /sbin/init casjaysdev/xorg

Default template for app repos is alpine unless the application needs systemd, a GUI stack, or a distro-specific package.

Final-stage templates

scratch.template — all non-GUI templates.

  • ENTRYPOINT [ "tini", "-p", "SIGTERM","--", "/usr/local/bin/entrypoint.sh" ]
  • STOPSIGNAL SIGRTMIN+3

systemd.templateweb and xorg (systemd is PID 1; tini is redundant).

  • ENTRYPOINT [ "/sbin/init" ]
  • STOPSIGNAL SIGRTMIN+3
  • No tini_provider stage, no COPY --from=tini_provider line.

Both are identical apart from ENTRYPOINT. OCI labels, ENV HOSTNAME, and VOLUME/EXPOSE/HEALTHCHECK are the same in both.

OCI label standard

Both final-stage templates emit these labels (no others):

LABEL maintainer="${GEN_DOCKERFILE_MAINTAINER}"
LABEL org.opencontainers.image.vendor="${GEN_DOCKERFILE_VENDOR:-CasjaysDev}"
LABEL org.opencontainers.image.authors="${GEN_DOCKERFILE_AUTHOR:-CasjaysDev}"
LABEL org.opencontainers.image.licenses="${LICENSE}"
LABEL org.opencontainers.image.title="${IMAGE_NAME}"
LABEL org.opencontainers.image.description="Containerized version of ${IMAGE_NAME}"
LABEL org.opencontainers.image.created="${BUILD_DATE}"
LABEL org.opencontainers.image.version="${BUILD_VERSION}"
LABEL org.opencontainers.image.revision="${GIT_COMMIT}"
LABEL org.opencontainers.image.url="${GEN_DOCKERFILE_HUB_REPO}"
LABEL org.opencontainers.image.source="${GEN_DOCKERFILE_GIT_REPO}"
LABEL org.opencontainers.image.documentation="${GEN_DOCKERFILE_GIT_REPO}"
LABEL org.opencontainers.image.vcs-type="Git"
LABEL com.github.containers.toolbox="false"

Shell-expanded values (no \) are evaluated at template-render time by gen-dockerfile. Dollar-escaped values (\${...}) become literal Docker ARG/ENV references in the generated Dockerfile.

Resolved values for a casjaysdevdocker repo pushing to Docker Hub:

Label Value
url https://hub.docker.com/r/casjaysdevdocker/{name} — browsable Hub page; gen-dockerfile derives it from the registry host (docker.iohub.docker.com/r/)
source https://github.com/casjaysdevdocker/{name}
documentation https://github.com/casjaysdevdocker/{name}

Older app repos may still carry url="https://docker.io/casjaysdevdocker/{name}" — that is the stale form; regeneration corrects it. Removed labels (never re-add):

  • org.opencontainers.image.base.name — belongs on the base image, not this image
  • org.opencontainers.image.schema-version — non-spec; redundant with version
  • Any duplicate authors or source entries

HOSTNAME convention

All templates set ENV HOSTNAME="casjaysdevdocker-${IMAGE_NAME}" in every stage that declares it. The prefix is always casjaysdevdocker-, never casjaysdev-.

GEN_DOCKERFILE_APP_DIR and pull URL logic

GEN_DOCKERFILE_APP_DIR is auto-detected by gen-dockerfile from the parent directory of $PWD (the org the checkout lives in):

GEN_DOCKERFILE_APP_DIR="${GEN_DOCKERFILE_APP_DIR:-$(basename -- "$(dirname -- "$PWD")")}"

It selects the GEN_DOCKER_SPECIFY_IMAGE_SOURCE_* defaults:

  • casjaysdevdocker/* repos → FROM casjaysdev/<distro>:latest (pre-built, multi-arch)
  • dockersrc/* and all other orgs → FROM <distro>:latest (upstream official images)

App repos must resolve to the casjaysdev/* branch — a checkout outside ~/Projects/*/casjaysdevdocker/ needs GEN_DOCKERFILE_APP_DIR="casjaysdevdocker" exported before calling gen-dockerfile, or the regenerated Dockerfile silently reverts to upstream distro pulls (rule 8 violation).

Arch Linux apps

casjaysdev/archlinux is a multi-arch manifest (linux/amd64 + linux/arm64), so app repos use a single FROM ${PULL_URL}:${DISTRO_VERSION} AS build — the three-stage base-${TARGETARCH} FROM block belongs to the base repo only.

web.template / xorg.template notes

web apps inherit the systemd + noVNC stack (SERVICE_PORT="5800", EXPOSE_PORTS="5800 5900" defaults); xorg apps inherit the systemd + Xorg stack. App packages go in ENV_PACKAGES / 02-packages.sh, never by editing the template's stack list.

debian.template / ubuntu.template — RUN continuation

The first RUN block must have ; \ after the echo line so export DEBIAN_FRONTEND=noninteractive executes before apt-get:

RUN set -e; \
  echo "Updating the system"; \
  export DEBIAN_FRONTEND=noninteractive; \
  apt-get update && apt-get upgrade -yy && apt-get dist-upgrade -yy

Without the ; \ the export is a no-op and apt-get may prompt interactively.

Template resolution order

  1. $GEN_DOCKERFILE_CONFIG_DIR/templates/<name>.template (user override)
  2. /usr/local/share/CasjaysDev/scripts/templates/dockerfiles/<name>.template (installed; $CASJAYSDEVDIR/templates/dockerfiles/ in a dev checkout)

template_options.source is sourced after __set_variables, allowing template-specific variable overrides.


PART 3: TOOLING — gen-dockerfile & gen-script

gen-dockerfile

Usage: gen-dockerfile [options] [dir] [template] [repo-name] [git-repo-url]
Flag Meaning
--update Rewrite .env.scripts (add/drop vars against the current template) and update ARG/LABEL lines in the Dockerfile. Touches no other file.
--nogit Do not init or commit a git repo — required inside an existing repo.
--dir PATH Operate on / write output to PATH instead of $PWD.
--template NAME Template to use (alpine, debian, ubuntu, rhel, archlinux, scratch, web, xorg). Defaults to alpine.
--repo NAME Registry repo name (image basename). Defaults to the directory name.
--org NAME Registry owner / GitHub org (--user is an alias). Prefix git: or reg: to scope to one system; bare value sets both. For app repos both are casjaysdevdocker.
--registry URL Registry provider URL (e.g. https://docker.io).
--tag VERSION Image version tag (default latest).
--add-tags TAGS Comma-separated additional tags (USE_DATE = auto date tag).
--distro-name IMG Base image pull URL (overrides ENV_PULL_URL).
--distro-version T Base image tag (overrides ENV_DISTRO_TAG).
--startup FILE Generate an init.d service script at rootfs/usr/local/etc/docker/init.d/FILE via gen-script other/start-service.
--dockerfile Regenerate the Dockerfile only.
--force Overwrite existing files without prompting.

Resolution order when a value is not given by a flag: flags → git remote → project dirs → defaults.

Special subcommand — gen-dockerfile actions writes .gitea/workflows/build.yml from the existing Dockerfile (PART 7). App repos have no versioned build.{ver}.yml files.

gen-script

Usage: gen-script [options] [template] [filename]
Flag / env var Meaning
--dir PATH Write the generated file to PATH/filename.
-n / --name VALUE Service name substituted into the template — fills REPLACE_SERVICE_NAME in other/start-service, pre-populating SERVICE_NAME= without a sed step.
GEN_SCRIPT_OVERWRITE="Y" Overwrite the output without prompting (default "A" = ask). Required when the target exists, even with GEN_SCRIPT_EDITFILE="N".
GEN_SCRIPT_EDITFILE="N" Suppress the interactive editor after generation. -e/--no sets BOTH this AND GEN_SCRIPT_OVERWRITE="Y"; the env var alone does not.
other/start-service Template path — positional arg 1, slash-joined words, matching the @@Template header.
filename Output basename — positional arg 2, combined with --dir.

Other flags: -k/--keep (never overwrite), --replace (new header replaces old), -d/--desc (header description), -p/--prev (copy header metadata from a file).


PART 4: .env.scripts REFERENCE

Generated at the repo root; sourced by gen-dockerfile and by CI at build time. App repos carry exactly one. It is a pure KEY="value" file — no logic.

Variables

Variable Purpose
ENV_DOCKERFILE Dockerfile to build (Dockerfile)
ENV_REGISTRY_REPO Image name in the registry ({name})
ENV_REGISTRY_ORG Registry namespace — casjaysdevdocker for app repos
ENV_REGISTRY_URL Registry base URL (https://docker.io) — pull/push host, never a label URL
ENV_REGISTRY_PUSH Full push path org/repo (casjaysdevdocker/{name})
ENV_ADD_IMAGE_PUSH Extra push destinations
ENV_GIT_REPO_URL Full Git repo URL — https://github.com/casjaysdevdocker/{name}; feeds the source/documentation labels, so a wrong value here regresses labels on regeneration
ENV_USE_TEMPLATE Template name (alpine, debian, …) — the authoritative record of which base family the app builds on
ENV_PULL_URL Base image to pull FROM (casjaysdev/<base>)
ENV_DISTRO_TAG Tag for the pull image (latest)
ENV_IMAGE_TAG Default image tag (latest)
ENV_ADD_TAGS Additional comma-separated tags; USE_DATE auto-generates a date tag
ENV_PACKAGES Space-separated package list
ENV_VENDOR / ENV_AUTHOR / ENV_MAINTAINER Label metadata
SERVICE_PORT Primary exposed port — apps normally set this
EXPOSE_PORTS Additional exposed ports
PHP_VERSION / NODE_VERSION / NODE_MANAGER Runtime versions (system default)
WWW_ROOT_DIR Web root (/usr/local/share/httpd/default)
DOCKER_ENTYPOINT_PORTS_WEB / DOCKER_ENTYPOINT_PORTS_SRV Ports passed to the entrypoint
DOCKER_ENTYPOINT_HEALTH_APPS / DOCKER_ENTYPOINT_HEALTH_ENDPOINTS Healthcheck targets

Legacy variable auto-migration

gen-dockerfile calls __migrate_env_script on every run, renaming old variables:

Old name Current name
ENV_IMAGE_NAME ENV_REGISTRY_REPO
ENV_IMAGE_PUSH ENV_REGISTRY_PUSH
ENV_HUB_BASE ENV_REGISTRY_URL
ENV_ORG_NAME ENV_REGISTRY_ORG

Never use the old names in new files. Retired variables that must not reappear anywhere: DEFAULT_TEMPLATE_DIR, DEFAULT_FILE_DIR, DEFAULT_DATA_DIR, DEFAULT_CONF_DIR.


PART 5: RUNTIME SYSTEM — SETUP SCRIPTS, ENTRYPOINT, INIT.D

Build-time setup scripts (rootfs/root/docker/setup/)

Run in order inside the build stage:

Script Role
00-init.sh Initialize base directory structure and environment
01-system.sh Repos, locales, timezone, system settings
02-packages.sh App-specific packages, package managers, language runtimes
03-files.sh Install staged files (rootfs/tmp/etc/*/etc/*), permissions, symlinks
04-users.sh Create service users/groups
05-custom.sh Application install logic — the heart of an app repo
06-post.sh Post-install configuration
07-cleanup.sh Remove build deps, caches, temp files

05-custom.sh ownership: the upstream template ships an empty stub. An app repo's 05-custom.sh carries the application install (download/build, users, default config) — that content exists only in the repo's git history, never in the template. On regeneration, keep the existing body and pull forward only boilerplate (version-stamp header, set line, shellcheck-disable line). The same rule applies to any other 0*.sh found to contain real logic beyond the stub.

Entrypoint flow

tini → /usr/local/bin/entrypoint.sh
├─ Load /usr/local/etc/docker/functions/entrypoint.sh
├─ Source env: /root/env.sh, /usr/local/etc/docker/env/*.sh, /config/env/*.sh
├─ Seed /config and /data on first run
├─ __start_init_scripts — source every init.d/*.sh in sort order
├─ Handle `healthcheck` command
└─ Execute main application

rootfs/usr/local/bin/ generated set: entrypoint.sh, pkmgr, symlink, copy, healthcheck. pkmgr wraps the native package manager (apk, apt-get, dnf, pacman) behind pkmgr update|install|remove|clean.

App-specific bin scripts

Extra scripts in rootfs/usr/local/bin/ that gen-dockerfile does not generate are repo-owned. Their @@Template header governs maintenance:

  • @@Template : shell/sh — boilerplate synced from $TEMPLATE_DIR/scripts/shell/sh; #!/usr/bin/env sh, set -e only (pipefail is a bashism — must NOT appear)
  • @@Template : shell/bash — synced from shell/bash; set -eo pipefail required
  • No @@Template header — hand-written; never modified by tooling

init.d scripts — critical rules

Each service gets its own numbered init.d script. Never merge or remove services. __start_init_scripts sources every *.sh in sort order — multi-process apps have one script per daemon (e.g. gitea: 05-dockerd.sh, 08-gitea.sh, zz-act_runner.sh).

init.d scripts are regenerated, never patched in place — old copies may call functions removed from the current functions/entrypoint.sh. Generate fresh via gen-script other/start-service (or gen-dockerfile --startup), then restore the app-specific values. They are #!/usr/bin/env bash with set -eo pipefail.

Required variables in every init.d script:

SERVICE_NAME="myapp"
EXEC_CMD_BIN='myapp'
EXEC_CMD_ARGS=''
EXEC_PRE_SCRIPT=''
SERVICE_USES_PID=''
IS_WEB_SERVER="no"
IS_DATABASE_SERVICE="no"
USES_DATABASE_SERVICE="no"
DATABASE_SERVICE_TYPE="sqlite"
RUNAS_USER="root"

Directory variables:

DATA_DIR="/data/$SERVICE_NAME"
CONF_DIR="/config/$SERVICE_NAME"
ETC_DIR="/etc/$SERVICE_NAME"
LOG_DIR="/data/logs/$SERVICE_NAME"
TMP_DIR="/tmp/$SERVICE_NAME"
RUN_DIR="/run/$SERVICE_NAME"
ROOT_FILE_PREFIX="/config/secure/auth/root"
USER_FILE_PREFIX="/config/secure/auth/user"

Hook functions

The start-service template generates all outer hooks fully implemented — customise via the matching *_local() stub, which each outer hook calls automatically if defined:

Outer hook (do not redefine) Customise via
__run_precopy __run_precopy_local
__execute_prerun __execute_prerun_local
__run_pre_execute_checks __run_pre_execute_checks_local
__update_conf_files __update_conf_files_local
__pre_execute __pre_execute_local
__post_execute __post_execute_local
__pre_message __pre_message_local
__update_ssl_conf __update_ssl_conf_local
__create_service_env
__run_start_script
__run_secure_function

PID sentinel guard

Every init.d script must guard on exactly this sentinel — leading dot, no underscores in the filename portion; any other form silently skips the guard:

if [ ! -f "/run/.start_init_scripts.pid" ]; then
  echo "__start_init_scripts function hasn't been Initialized" >&2
  SERVICE_IS_RUNNING="no"
  __script_exit 1
fi

Volumes

  • /config — persistent configuration
  • /data — persistent application data

PART 6: README.md STANDARD LAYOUT

App image layout (casjaysdevdocker/{name}casjaysdevdocker/{name}). Substitute {name} and {port} (the value of SERVICE_PORT); omit all -p/ports: sections only in the rare case SERVICE_PORT is empty.

Hand-crafted README exception: a repo whose README deliberately diverges from this layout (full env-var tables, app-specific quick-start flags — e.g. gitea) owns its README. Update its facts (image name, org, ports, URLs), never rewrite its structure back to the generated layout.

## 👋 Welcome to {name} 🚀

{name} README


## Install my system scripts

```shell
 sudo bash -c "$(curl -q -LSsf "https://github.com/systemmgr/installer/raw/main/install.sh")"
 sudo systemmgr --config && sudo systemmgr install scripts
```

## Automatic install/update

```shell
dockermgr update {name}
```

## Install and run container

```shell
dockerHome="/srv/$USER/docker/casjaysdevdocker/{name}/latest/volumes"
mkdir -p "$dockerHome"
git clone "https://github.com/dockermgr/{name}" "$HOME/.local/share/CasjaysDev/dockermgr/{name}"
cp -Rfva "$HOME/.local/share/CasjaysDev/dockermgr/{name}/volumes/." "$dockerHome/"
docker run -d \
--restart always \
--privileged \
--name casjaysdevdocker-{name}-latest \
--hostname {name} \
-e TZ=${TIMEZONE:-America/New_York} \
-v "$dockerHome/data:/data:z" \
-v "$dockerHome/config:/config:z" \
-p {port}:{port} \
casjaysdevdocker/{name}:latest
```

## via docker-compose

```yaml
services:
  ProjectName:
    image: casjaysdevdocker/{name}
    container_name: casjaysdevdocker-{name}
    environment:
      - TZ=America/New_York
      - HOSTNAME={name}
    volumes:
      - "/srv/$USER/docker/casjaysdevdocker/{name}/latest/volumes/data:/data:z"
      - "/srv/$USER/docker/casjaysdevdocker/{name}/latest/volumes/config:/config:z"
    ports:
      - {port}:{port}
    restart: always
```

## Get source files

```shell
dockermgr download src casjaysdevdocker/{name}
```

OR

```shell
git clone "https://github.com/casjaysdevdocker/{name}" "$HOME/Projects/github/casjaysdevdocker/{name}"
```

## Build container

```shell
cd "$HOME/Projects/github/casjaysdevdocker/{name}"
buildx
```

## Authors

🤖 casjay: [Github](https://github.com/casjay) 🤖
⛵ casjaysdevdocker: [Github](https://github.com/casjaysdevdocker) [Docker](https://hub.docker.com/u/casjaysdevdocker) ⛵

PART 7: CI/CD WORKFLOWS

Generated workflow (gen-dockerfile actions)

gen-dockerfile actions writes .gitea/workflows/build.yml from the current Dockerfile. App repos get the single build.yml only — no versioned variants. All actions are SHA-pinned — never tag-pinned.

  • Triggers: push to main, monthly schedule, workflow_dispatch
  • Registry strategy: always logs in to the Gitea registry via the auto-provided GITEA_TOKEN; conditionally logs in to Docker Hub when vars.DOCKER_USERNAME is set (vars.DOCKER_USERNAME + secrets.DOCKER_PASSWORD; vars.DOCKER_REGISTRY overrides the registry, vars.DOCKER_ORG the namespace)
  • Platforms: linux/amd64,linux/arm64
  • build-args: only BUILD_DATE, GIT_COMMIT, BUILD_VERSION
  • Tags pushed: date tag (yymm) + latest to both registries
  • Annotations: mirror the OCI label standard (PART 2), with url/source/ documentation set to the workflow's repository URL

Legacy workflow (docker.yaml)

A hand-crafted .gitea/workflows/docker.yaml may exist in older repos — reference copy in the org-level .github repo. Never overwrite it, and never use it as a template for new work — it uses tag-pinned actions and retired secret names. All new/updated workflows come from gen-dockerfile actions.


PART 8: VERIFICATION & COMMIT

Syntax gates

Every touched script must pass before commit:

for f in rootfs/usr/local/bin/*; do
  [ -f "$f" ] || continue
  case "$(head -1 "$f")" in
    *bash*) bash -n "$f" || exit 1 ;;
    *sh*)   sh -n "$f"   || exit 1 ;;
  esac
done

bash -n rootfs/usr/local/etc/docker/functions/entrypoint.sh

for f in rootfs/root/docker/setup/0*.sh rootfs/usr/local/etc/docker/init.d/*.sh; do
  [ -f "$f" ] || continue
  bash -n "$f" || exit 1
done

Dead-reference gates

After any regeneration:

  1. No script references an env var removed from .env.scripts (diff-driven check).
  2. No script calls a function absent from both the current functions/entrypoint.sh and the script itself.
  3. No __copy_templates calls remain (retired with DEFAULT_TEMPLATE_DIR).
  4. Dockerfile still pulls FROM casjaysdev/* (rule 8) — an upstream distro pull means GEN_DOCKERFILE_APP_DIR resolved wrong during regeneration.

Commit

git status --porcelain
git diff --stat

Write .git/COMMIT_MESS from the actual diff — subject ≤64 chars, body as - path: change bullets covering every changed file. Then:

gitcommit --dir "$(git rev-parse --show-toplevel)" all

git commit / git push directly are forbidden. Never commit with a failing syntax gate.