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
28 KiB
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
AI.mdis permanent — never delete it from the repo.- Generated files are owned by the template system — never hand-tune content that
gen-dockerfileregenerates (see PART 1 ownership table); fix the upstreamgen-dockerfiletemplate instead, then regenerate. - Hand-crafted files are owned by the repo —
gen-dockerfilemust never overwrite app-specific init.d scripts, custom bin scripts, a05-custom.shwith real content, or a hand-crafted README (PART 6). - Removed OCI labels stay removed (PART 2) — never re-add
base.name,schema-version, or duplicateauthors/sourceentries. image.urlis a browsable page —https://hub.docker.com/r/casjaysdevdocker/{name}.docker.iois only a registry pull host; it is never a label URL.image.sourceandimage.documentationare the GitHub repo —https://github.com/casjaysdevdocker/{name}.- One Dockerfile, one file set — app repos build one image (
latest+ date tag); version variants (Dockerfile.{ver}) belong to base repos only. - Always
FROM casjaysdev/<base>— never pull an upstream distro image directly; the base repos exist so every app shares one patched, multi-arch foundation. - Only
root/,tmp/, andusr/may exist atrootfs/top level (PART 1). - Maintenance runs through the
dockersrc-bootstrapagent — 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 00–07
├── 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.template — web and xorg (systemd is PID 1; tini is redundant).
ENTRYPOINT [ "/sbin/init" ]STOPSIGNAL SIGRTMIN+3- No
tini_providerstage, noCOPY --from=tini_providerline.
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.io → hub.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 imageorg.opencontainers.image.schema-version— non-spec; redundant withversion- Any duplicate
authorsorsourceentries
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
$GEN_DOCKERFILE_CONFIG_DIR/templates/<name>.template(user override)/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 -eonly (pipefailis a bashism — must NOT appear)@@Template : shell/bash— synced fromshell/bash;set -eo pipefailrequired- No
@@Templateheader — 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:
pushtomain, 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 whenvars.DOCKER_USERNAMEis set (vars.DOCKER_USERNAME+secrets.DOCKER_PASSWORD;vars.DOCKER_REGISTRYoverrides the registry,vars.DOCKER_ORGthe namespace) - Platforms:
linux/amd64,linux/arm64 - build-args: only
BUILD_DATE,GIT_COMMIT,BUILD_VERSION - Tags pushed: date tag (
yymm) +latestto both registries - Annotations: mirror the OCI label standard (PART 2), with
url/source/documentationset 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:
- No script references an env var removed from
.env.scripts(diff-driven check). - No script calls a function absent from both the current
functions/entrypoint.shand the script itself. - No
__copy_templatescalls remain (retired withDEFAULT_TEMPLATE_DIR). Dockerfilestill pullsFROM casjaysdev/*(rule 8) — an upstream distro pull meansGEN_DOCKERFILE_APP_DIRresolved 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.