Files
.github/AI.md
T
jason 4fda116227 🐛 Fix xorg pull source — casjaysdev/xorg does not exist 🐛
Verified against Docker Hub API: casjaysdev/web exists,
casjaysdev/xorg returns 404. Update AI.md to reflect reality
and warn that ENV_PULL_URL must be set for xorg projects.
- AI.md: mark casjaysdev/xorg as non-existent; add must-set
ENV_PULL_URL warning for xorg template
- AI.md: clarify web/xorg package additions (xorg + x11-apps)
rather than implying they use a pre-built xorg-specific image

AI.md
2026-06-26 13:04:10 -04:00

32 KiB

Docker Container Repository Specification

Repository Overview

Repository: casjaysdevdocker
Maintainer: CasjaysDev docker-admin@casjaysdev.pro
License: WTFPL / MIT
Purpose: Collection of containerized applications using standardized Alpine-based templates


Repository Structure

This repository contains multiple Docker container projects organised as subdirectories. Each project follows a consistent structure and naming convention.

Directory Layout

casjaysdevdocker/
├── ampache/           # Media streaming server
├── apprise/           # Notification service
├── aria2/             # Download manager
├── bind/              # DNS server
├── bun/               # JavaScript runtime
├── caddy/             # Web server
├── code/              # VS Code server
├── couchdb/           # NoSQL database
├── ddns/              # Dynamic DNS client
├── deno/              # JavaScript runtime
├── docker/            # Docker-in-Docker
├── enclosed/          # Encrypted note sharing
├── forgejo/           # Git forge
├── gitea/             # Git service
├── gotify/            # Push notification server
├── i2pd/              # I2P router
├── icecast/           # Audio streaming
├── ifconfig/          # IP info service
├── jekyll/            # Static site generator
├── lighttpd/          # Web server
├── mailman/           # Mailing list manager
├── mariadb/           # SQL database
├── mongodb/           # NoSQL database
├── mpd/               # Music player daemon
├── mysql/             # SQL database
├── navidrome/         # Music streaming
├── neovim/            # Text editor
├── nextcloud/         # Cloud storage
├── nginx/             # Web server
├── nodejs/            # Node.js runtime
├── ntfy/              # Notification service
├── ollama/            # LLM runtime
├── opencloud/         # Cloud platform
├── opengist/          # Gist clone
├── pastebin/          # Pastebin service
├── php/               # PHP runtime
├── podman/            # Container runtime
├── postfix/           # Mail server
├── postgres/          # SQL database
├── proftpd/           # FTP server
├── python/            # Python runtime
├── redis/             # In-memory database
├── soft-serve/        # Git server
├── sqlite/            # SQL database
├── ssl-ca/            # SSL certificate authority
├── tftpd/             # TFTP server
├── tor/               # Tor relay
├── traefik/           # Reverse proxy
├── transmission/      # BitTorrent client
├── valkey/            # Redis fork
├── vim/               # Text editor
├── webmin/            # Web admin interface
├── wordpress/         # CMS platform
├── wttr/              # Weather service
├── xfce4/             # Desktop environment
├── ympd/              # MPD web client
├── youtube-dl/        # Video downloader
└── tools/             # Shared tools & scripts

Standard Project Structure

Each project directory contains:

project-name/
├── .dockerignore              # [generated] Docker build exclusions
├── .env.scripts               # [generated] Environment variables for gen-dockerfile
├── .git/                      # Git repository
├── .gitattributes             # [generated] Git attributes
├── .gitea/workflows/
│   ├── docker.yaml            # [hand-crafted] Legacy CI workflow — do not overwrite
│   └── build.yml              # [generated] by gen-dockerfile actions — regenerate freely
├── .gitignore                 # [generated] Git ignore patterns
├── Dockerfile                 # [generated] Container build definition
├── LICENSE.md                 # License information
├── README.md                  # [generated] Project documentation
└── rootfs/                    # Container filesystem overlay
    ├── root/docker/setup/     # [generated] Build-time setup scripts
    │   ├── 00-init.sh
    │   ├── 01-system.sh
    │   ├── 02-packages.sh
    │   ├── 03-files.sh
    │   ├── 04-users.sh
    │   ├── 05-custom.sh
    │   ├── 06-post.sh
    │   └── 07-cleanup.sh
    └── usr/local/
        ├── bin/
        │   ├── entrypoint.sh      # [generated] Container entrypoint
        │   └── pkmgr              # [generated] Package manager wrapper
        ├── etc/docker/
        │   ├── functions/
        │   │   └── entrypoint.sh  # [generated] Entrypoint functions
        │   └── init.d/            # [hand-crafted] Runtime init scripts (one per service)
        └── share/template-files/  # [hand-crafted] Config/data templates
            ├── config/
            ├── data/
            └── defaults/

[generated] — safe to overwrite with gen-dockerfile --update; changes will be lost on next regeneration.
[hand-crafted] — must not be overwritten by gen-dockerfile; customised per container.


gen-dockerfile Tool

All containers are built using the gen-dockerfile tool. It generates standardised Dockerfiles, .env.scripts, workflow YAML, pkmgr scripts, and rootfs/ scaffolding.

Usage

gen-dockerfile [options] [template] [Dockerfile]
gen-dockerfile --dir ./myapp alpine
gen-dockerfile --dir ./myapp --nginx --tag 1.25
gen-dockerfile --dir ./existing-project --update
gen-dockerfile --dir ./myapp actions

Templates (positional argument or --template)

Template Description Pull source (build stage)
alpine Alpine Linux (default) alpine (Docker Hub official)
arch / archlinux Arch Linux ARM menci/archlinuxarm
debian Debian debian
ubuntu Ubuntu ubuntu
rhel / almalinux / rockylinux / centos / oraclelinux / redhat AlmaLinux/RHEL family almalinux
web Adds xorg + x11-apps packages on top of the build stage; scratch final image casjaysdev/web (exists) — override via ENV_PULL_URL
xorg Adds xorg + x11-apps packages on top of the build stage; scratch final image casjaysdev/xorg (does not exist) — must set ENV_PULL_URL
scratch Scratch final image only (pair with any build template) N/A

Note: The pull source is the base image pulled for the build stage. All templates produce a FROM scratch final stage. For standard distros (alpine, debian, ubuntu, rhel, arch), the pull source is the official upstream image. For web and xorg, gen-dockerfile defaults to casjaysdev/<template> when no ENV_PULL_URL is set — casjaysdev/web exists on Docker Hub; casjaysdev/xorg does not. Always set ENV_PULL_URL in .env.scripts for xorg projects.

CLI Flags

Flag Description
--dir <path> Working directory (default: $PWD)
--template <name> Override template type
--registry <url> Registry provider URL (e.g. https://docker.io)
--org <[git|reg:]name> Override org/namespace — prefix git: or reg: to scope to one system, bare value sets both
--user <[git|reg:]name> Alias for --org
--repo <[git|reg:]name> Override repo/image name — same scope prefix rules
--tag <version> Image version tag (default: latest)
--add-tags <tags> Comma-separated additional tags (or USE_DATE for auto date tag)
--distro-name <image> Base image pull URL (overrides ENV_PULL_URL)
--distro-version <tag> Base image tag (overrides ENV_DISTRO_TAG)
--startup Generate an init.d service script at rootfs/usr/local/etc/docker/init.d/<name> via gen-script other start-service; takes the script filename as a positional argument (e.g. gen-dockerfile --startup 01-myapp.sh)
--nogit Skip git init in the new project dir
--dockerfile Regenerate Dockerfile only — skip other files
--update Re-run gen-dockerfile on an existing project to regenerate files
--init Init mode
--x11 Enable X11/xorg support
--ports <ports> Additional EXPOSE ports
--pkmgr Generate pkmgr script only and exit
--apache Add Apache2 packages + setup
--nginx Add nginx packages + setup
--mysql / --mariadb Add MariaDB packages + setup
--postgres Add PostgreSQL packages + setup
--php Add PHP packages + setup
--application <name> Add a templatemgr application install step
--alpine / --almalinux / --archlinux / --debian / --ubuntu Shortcut distro flags
--force Overwrite existing files without prompting
--silent Suppress non-error output
--debug Enable set -x tracing
--no-color Disable colour output

Special Subcommands

gen-dockerfile --dir ./myapp actions

Creates .gitea/workflows/build.yml (or build.$version.yml for versioned tags) from the existing Dockerfile. The canonical reference for the legacy hand-written style is .gitea/workflows/docker.yaml in the casjaysdevdocker/.github org-level repository (casjaysdevdocker/.github/example/); gen-dockerfile actions generates the modern build.yml — use that for all new containers.

Template Resolution

Templates are loaded from the first match:

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

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


.env.scripts File

Generated at project root; sourced by gen-dockerfile and by the CI workflow at build time.

Current Variable Names

# - - - - - - - - - - - - - - - - - - - - - - - - -
##@Version           :  202601291955-git
# ...
# - - - - - - - - - - - - - - - - - - - - - - - - -

# Entrypoint Settings
DOCKER_ENTYPOINT_PORTS_WEB="${DOCKER_ENTYPOINT_PORTS_WEB}"
DOCKER_ENTYPOINT_PORTS_SRV="${DOCKER_ENTYPOINT_PORTS_SRV}"
DOCKER_ENTYPOINT_HEALTH_APPS="$DOCKER_ENTYPOINT_HEALTH_APPS"
DOCKER_ENTYPOINT_HEALTH_ENDPOINTS="$DOCKER_ENTYPOINT_HEALTH_ENDPOINTS"
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Dockerfile info
ENV_DOCKERFILE="Dockerfile"
# ENV_REGISTRY_REPO: Registry repository/image name
ENV_REGISTRY_REPO="myapp"
ENV_USE_TEMPLATE="alpine"
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Maintainer info
ENV_REGISTRY_ORG="casjaysdevdocker"
ENV_VENDOR="CasjaysDev"
ENV_AUTHOR="CasjaysDev"
ENV_MAINTAINER="CasjaysDev <docker-admin@casjaysdev.pro>"
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Repository URLs (Full URLs)
# ENV_GIT_REPO_URL: Complete Git repository URL for source code
ENV_GIT_REPO_URL="https://github.com/casjaysdevdocker/myapp"
# ENV_REGISTRY_URL: Registry provider base URL
ENV_REGISTRY_URL="https://docker.io"
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Push Configuration
# ENV_REGISTRY_PUSH: Complete push destination (registry/org/repo)
ENV_REGISTRY_PUSH="casjaysdevdocker/myapp"
# ENV_IMAGE_TAG: Default tag for the image
ENV_IMAGE_TAG="latest"
# ENV_ADD_TAGS: Additional tags, comma-separated (USE_DATE = auto date tag)
ENV_ADD_TAGS=""
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Additional push destinations (if needed)
ENV_ADD_IMAGE_PUSH=""
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Pull Configuration
# ENV_PULL_URL: Source image to pull from (base image)
ENV_PULL_URL="alpine"
# ENV_DISTRO_TAG: Tag for the pull source image
ENV_DISTRO_TAG="${IMAGE_VERSION}"
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Port exposure
SERVICE_PORT=""
EXPOSE_PORTS=""
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Language runtime version (go, php, rust, ruby, etc)
LANG_VERSION=""
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Runtime versions
PHP_VERSION="system"
NODE_VERSION="system"
NODE_MANAGER="system"
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Default directories
WWW_ROOT_DIR="${WWW_ROOT_DIR:-/usr/local/share/httpd/default}"
# - - - - - - - - - - - - - - - - - - - - - - - - -
ENV_PACKAGES=""

Variable Reference

Variable Purpose
ENV_REGISTRY_REPO Image name in the registry
ENV_REGISTRY_ORG Organisation/namespace in the registry
ENV_REGISTRY_URL Registry base URL (e.g. https://docker.io)
ENV_REGISTRY_PUSH Full push path org/repo (registry host prepended if not docker.io)
ENV_ADD_IMAGE_PUSH Extra push destinations
ENV_GIT_REPO_URL Full Git repository URL
ENV_USE_TEMPLATE Template name to use (alpine, debian, etc.)
ENV_PULL_URL Base image to pull from
ENV_DISTRO_TAG Tag for the base image
ENV_IMAGE_TAG Default image tag (default: latest)
ENV_ADD_TAGS Additional comma-separated tags; USE_DATE auto-generates a YYMM tag
ENV_PACKAGES Space-separated package list to install
ENV_VENDOR / ENV_AUTHOR / ENV_MAINTAINER Label metadata
SERVICE_PORT Primary exposed port
EXPOSE_PORTS Additional exposed ports
LANG_VERSION Language runtime version (go, php, rust, ruby, etc.)
PHP_VERSION / NODE_VERSION / NODE_MANAGER Runtime versions
WWW_ROOT_DIR Web root directory (default: /usr/local/share/httpd/default)
DOCKER_ENTYPOINT_PORTS_WEB Web ports passed to entrypoint
DOCKER_ENTYPOINT_PORTS_SRV Service ports passed to entrypoint
DOCKER_ENTYPOINT_HEALTH_APPS Apps to health-check
DOCKER_ENTYPOINT_HEALTH_ENDPOINTS Endpoints to health-check

Legacy Variable Auto-Migration

gen-dockerfile --update (and on every run) calls __migrate_env_script which automatically renames old variable names to the current ones. Old names that are no longer used:

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 .env.scripts files.


Dockerfile Template Structure

Build Arguments

Top-of-file ARGs (before any FROM):

Argument Description Default
IMAGE_NAME Container name (from ENV_REGISTRY_REPO)
PHP_SERVER PHP server name (same as IMAGE_NAME)
BUILD_DATE Build timestamp YYYYMMDDHHMM
LANGUAGE System locale en_US.UTF-8
TIMEZONE System timezone America/New_York
WWW_ROOT_DIR Web root /usr/local/share/httpd/default
PATH System PATH /usr/local/etc/docker/bin:/usr/local/sbin:...
USER Build user root
SHELL_OPTS Shell options set -e -o pipefail
SERVICE_PORT Primary service port (from .env.scripts)
EXPOSE_PORTS Additional ports (from .env.scripts)
PHP_VERSION PHP version system
NODE_VERSION Node.js version system
NODE_MANAGER Node manager system
IMAGE_REPO Full push path org/repo (computed)
IMAGE_VERSION Image version latest
CONTAINER_VERSION Additional image tags (computed)
PULL_URL Base image to pull alpine (official Docker Hub)
DISTRO_VERSION Base image tag ${IMAGE_VERSION}
BUILD_VERSION Build version alias ${BUILD_DATE}

Stage 2 (FROM scratch) additional ARGs:

Argument Description
TZ Timezone alias
GIT_COMMIT Git SHA at build time (from CI)
LICENSE Image license
ENV_PORTS Alias for ${EXPOSE_PORTS}

Generated Dockerfile Structure

Stage 1: Build Stage

FROM tianon/gosu:latest AS gosu
FROM ${PULL_URL}:${DISTRO_VERSION} AS build

# ... ARG declarations ...

ENV ENV=~/.profile
ENV SHELL="/bin/sh"
ENV PATH="${PATH}"
ENV TZ="${TIMEZONE}"
ENV TIMEZONE="${TZ}"
ENV LANG="${LANGUAGE}"
ENV TERM="xterm-256color"
ENV HOSTNAME="casjaysdevdocker-${IMAGE_NAME}"

COPY ./rootfs/. /
RUN pkmgr update; pkmgr install bash

ENV SHELL="/bin/bash"
SHELL ["/bin/bash", "-c"]
COPY --from=gosu /usr/local/bin/gosu /usr/local/bin/gosu

# Setup scripts run in order:
# 00-init.sh      - Initialize base system
# 01-system.sh    - Configure system settings
# 02-packages.sh  - Install/configure packages (after PACK_LIST install)
# 03-files.sh     - Copy/modify files
# 04-users.sh     - Create users/groups
# 05-custom.sh    - Custom application setup
# 06-post.sh      - Post-installation tasks
# 07-cleanup.sh   - Clean up temporary files

Stage 2: Final Image

FROM scratch

# ... ARG and LABEL declarations ...

ENV ENV=~/.bashrc
ENV USER="${USER}"
ENV PATH="${PATH}"
ENV TZ="${TIMEZONE}"
ENV SHELL="/bin/bash"
ENV TIMEZONE="${TZ}"
ENV LANG="${LANGUAGE}"
ENV TERM="xterm-256color"
ENV PORT="${SERVICE_PORT}"
ENV ENV_PORTS="${ENV_PORTS}"
ENV CONTAINER_NAME="${IMAGE_NAME}"
ENV HOSTNAME="casjaysdev-${IMAGE_NAME}"
ENV PHP_SERVER="${PHP_SERVER}"
ENV NODE_VERSION="${NODE_VERSION}"
ENV NODE_MANAGER="${NODE_MANAGER}"
ENV PHP_VERSION="${PHP_VERSION}"
ENV DISTRO_VERSION="${IMAGE_VERSION}"
ENV WWW_ROOT_DIR="${WWW_ROOT_DIR}"

COPY --from=build /. /

VOLUME [ "/config", "/data" ]
EXPOSE ${SERVICE_PORT} ${ENV_PORTS}
STOPSIGNAL SIGRTMIN+3
ENTRYPOINT [ "tini", "-p", "SIGTERM", "--", "/usr/local/bin/entrypoint.sh" ]
HEALTHCHECK --start-period=10m --interval=5m --timeout=15s \
  CMD [ "/usr/local/bin/entrypoint.sh", "healthcheck" ]

Container Labels

All containers include OpenContainers standard labels:

LABEL maintainer="CasjaysDev <docker-admin@casjaysdev.pro>"
LABEL org.opencontainers.image.vendor="CasjaysDev"
LABEL org.opencontainers.image.authors="CasjaysDev"
LABEL org.opencontainers.image.description="Containerized version of ${IMAGE_NAME}"
LABEL org.opencontainers.image.title="${IMAGE_NAME}"
LABEL org.opencontainers.image.base.name="${IMAGE_NAME}"
LABEL org.opencontainers.image.authors="${LICENSE}"
LABEL org.opencontainers.image.created="${BUILD_DATE}"
LABEL org.opencontainers.image.version="${BUILD_VERSION}"
LABEL org.opencontainers.image.schema-version="${BUILD_VERSION}"
LABEL org.opencontainers.image.url="https://hub.docker.com/r/casjaysdevdocker/${IMAGE_NAME}"
LABEL org.opencontainers.image.source="https://hub.docker.com/r/casjaysdevdocker/${IMAGE_NAME}"
LABEL org.opencontainers.image.vcs-type="Git"
LABEL org.opencontainers.image.revision="${GIT_COMMIT}"
LABEL org.opencontainers.image.source="https://github.com/casjaysdevdocker/${IMAGE_NAME}"
LABEL org.opencontainers.image.documentation="https://github.com/casjaysdevdocker/${IMAGE_NAME}"
LABEL com.github.containers.toolbox="false"

Note: org.opencontainers.image.source appears twice intentionally — first set to the Docker Hub URL, then overridden to the Git source URL. The second value wins at runtime.


Common Package List

Standard packages included in most containers (Alpine template):

bash-completion git curl wget sudo unzip iproute2 ssmtp openssl jq tzdata
mailcap ncurses util-linux pciutils usbutils coreutils binutils findutils
grep rsync zip tini py3-pip procps net-tools sed gawk attr readline lsof
less shadow ca-certificates

Setup Scripts Convention

Build-time scripts in rootfs/root/docker/setup/ run inside the Docker build layer.

00-init.sh

  • Initialize base directory structure
  • Set up template directories
  • Create initial environment

01-system.sh

  • Configure APK/apt/yum repositories
  • Set up system settings
  • Configure locales and timezone

02-packages.sh

  • Install application-specific packages
  • Configure package managers (pip, npm, etc.)
  • Set up language runtimes

03-files.sh

  • Copy configuration files
  • Set file permissions
  • Create symlinks

04-users.sh

  • Create application users
  • Set up user directories
  • Configure user permissions

05-custom.sh

  • Application-specific installation
  • Download/compile software
  • Custom configuration

06-post.sh

  • Post-installation configuration
  • Initialize databases
  • Generate default configs

07-cleanup.sh

  • Remove build dependencies
  • Clean package caches
  • Remove temporary files

Entrypoint System

Entrypoint Script Flow

/usr/local/bin/entrypoint.sh
├─ Load functions from /usr/local/etc/docker/functions/entrypoint.sh
├─ Set up environment variables (sources /root/env.sh, /usr/local/etc/docker/env/*.sh, /config/env/*.sh)
├─ Create /config and /data volumes
├─ Run ALL init scripts from /usr/local/etc/docker/init.d/ (sorted order) via __start_init_scripts
├─ Handle healthcheck command
└─ Execute main application

Init.d Scripts — CRITICAL RULES

Each service in a repo gets its own numbered init.d script. Never merge or remove them.

  • __start_init_scripts iterates and sources every *.sh file in init.d/ in sort order.
  • Multi-process repos have one script per service. Example for a repo running three daemons:
    init.d/01-named.sh     — BIND/named DNS server
    init.d/02-nginx.sh     — nginx web front-end
    init.d/03-php-fpm.sh   — PHP-FPM for web UI
    
  • Migration task = UPDATE each script to the canonical pattern, never delete services.
  • The canonical pattern is 04-example.sh in the casjaysdevdocker/.github org-level repository, or generate a fresh one with gen-dockerfile --startup <name>.

Required Variables in Every Init.d Script

SERVICE_NAME="myapp"           # used for PID files, log dirs, config dirs
EXEC_CMD_BIN='myapp'          # daemon binary (single-quoted — expanded later)
EXEC_CMD_ARGS=''               # daemon arguments (single-quoted)
EXEC_PRE_SCRIPT=''             # pre-start script (single-quoted)
SERVICE_USES_PID=''            # '' for long-running daemons; 'no' for config-only steps
IS_WEB_SERVER="no"             # 'yes' sets RESET_ENV and default port 80
IS_DATABASE_SERVICE="no"
USES_DATABASE_SERVICE="no"
DATABASE_SERVICE_TYPE="sqlite" # custom|sqlite|redis|postgres|mariadb|mysql|couchdb|mongodb|supabase
RUNAS_USER="root"

Required Hook Functions in Every Init.d Script

All eight must be defined (even if they just return 0):

__run_precopy()              # runs before copying /config to /etc
__execute_prerun()           # runs before service starts
__run_pre_execute_checks()   # validation before exec; non-zero aborts start
__update_conf_files()        # update config files (sed replacements, etc.)
__pre_execute()              # final setup before exec
__post_execute()             # background post-start tasks
__pre_message()              # message to show before exec
__update_ssl_conf()          # SSL certificate setup
__create_service_env()       # generates /config/env/$SERVICE_NAME.sh template
__run_start_script()         # builds and runs the exec script
__run_secure_function()      # chmod 600 on credential files

PID Sentinel Check (CRITICAL)

Every init.d script must guard on the correct sentinel file:

# Correct — dot prefix, no double underscore
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

The file is /run/.start_init_scripts.pid — a leading dot, no underscores in the filename portion. Any other form silently skips the guard.

Directory Variables

DATA_DIR="/data/$SERVICE_NAME"         # persistent data
CONF_DIR="/config/$SERVICE_NAME"       # persistent config
ETC_DIR="/etc/$SERVICE_NAME"           # runtime etc (synced from CONF_DIR)
LOG_DIR="/data/logs/$SERVICE_NAME"     # logs
TMP_DIR="/tmp/$SERVICE_NAME"
RUN_DIR="/run/$SERVICE_NAME"
ROOT_FILE_PREFIX="/config/secure/auth/root"
USER_FILE_PREFIX="/config/secure/auth/user"

Volume Mounts

  • /config — Persistent configuration files
  • /data — Persistent application data

CI/CD Integration

Two Workflow Files — Hand-Crafted vs Generated

File Origin Never overwrite?
.gitea/workflows/docker.yaml Hand-crafted legacy file; reference copy in casjaysdevdocker/.github/example/ Yes — do not overwrite
.gitea/workflows/build.yml Generated by gen-dockerfile actions No — regenerate freely

Generated Workflow (gen-dockerfile actions)

gen-dockerfile actions writes .gitea/workflows/build.yml (or build.<version>.yml for versioned tags) from the current Dockerfile. All actions are SHA-pinned.

Triggers: push to main, monthly schedule (cron: '0 2 1 * *'), workflow_dispatch.

Registry strategy:

  • Always logs in to the Gitea registry using the auto-provided GITEA_TOKEN.
  • Conditionally logs in to Docker Hub (or another registry) when vars.DOCKER_USERNAME is set; credentials via vars.DOCKER_USERNAME + secrets.DOCKER_PASSWORD.
  • vars.DOCKER_REGISTRY overrides the registry URL (default: docker.io).
  • vars.DOCKER_ORG overrides the namespace (fallback: vars.DOCKER_USERNAME).

build-args passed: only BUILD_DATE, GIT_COMMIT, BUILD_VERSION — not TIMEZONE, LANGUAGE, LICENSE, or TZ.

Tags pushed: <yymm> and latest to both Gitea and (if configured) Docker Hub.

name: Build and Push

on:
  push:
    branches: [main]
  schedule:
    - cron: '0 2 1 * *'
  workflow_dispatch:

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

    steps:
      - name: Checkout
        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

      - name: Set up QEMU
        uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3

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

      - name: Compute build metadata
        id: meta
        run: |
          echo "build_date=$(date -u +%Y%m%d%H%M)" >> "$GITHUB_OUTPUT"
          echo "tag_yymm=$(date -u +%y%m)" >> "$GITHUB_OUTPUT"
          echo "git_commit=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
          echo "registry_host=$(echo '${{ github.server_url }}' | sed 's|https://||')" >> "$GITHUB_OUTPUT"

      - name: Login to Gitea registry
        uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
        with:
          registry: ${{ steps.meta.outputs.registry_host }}
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITEA_TOKEN }}

      - name: Login to Docker Hub
        if: vars.DOCKER_USERNAME != ''
        uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
        with:
          registry: ${{ vars.DOCKER_REGISTRY || 'docker.io' }}
          username: ${{ vars.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Build and push
        uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          tags: |
            ${{ steps.meta.outputs.registry_host }}/${{ github.repository }}:${{ steps.meta.outputs.tag_yymm }}
            ${{ vars.DOCKER_USERNAME != '' && format('{0}/{1}/{2}:{3}', vars.DOCKER_REGISTRY || 'docker.io', vars.DOCKER_ORG || vars.DOCKER_USERNAME, github.event.repository.name, steps.meta.outputs.tag_yymm) || '' }}
            ${{ steps.meta.outputs.registry_host }}/${{ github.repository }}:latest
            ${{ vars.DOCKER_USERNAME != '' && format('{0}/{1}/{2}:{3}', vars.DOCKER_REGISTRY || 'docker.io', vars.DOCKER_ORG || vars.DOCKER_USERNAME, github.event.repository.name, 'latest') || '' }}
          build-args: |
            BUILD_DATE=${{ steps.meta.outputs.build_date }}
            GIT_COMMIT=${{ steps.meta.outputs.git_commit }}
            BUILD_VERSION=${{ steps.meta.outputs.tag_yymm }}
          annotations: |
            org.opencontainers.image.created=${{ steps.meta.outputs.build_date }}
            org.opencontainers.image.version=latest
            org.opencontainers.image.revision=${{ steps.meta.outputs.git_commit }}
            org.opencontainers.image.title=${{ github.event.repository.name }}
            org.opencontainers.image.description=Containerized version of ${{ github.event.repository.name }}
            org.opencontainers.image.vendor=CasjaysDev
            org.opencontainers.image.authors=CasjaysDev
            org.opencontainers.image.licenses=WTFPL
            org.opencontainers.image.url=${{ github.server_url }}/${{ github.repository }}
            org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
            org.opencontainers.image.documentation=${{ github.server_url }}/${{ github.repository }}
            org.opencontainers.image.vcs-type=Git
            com.github.containers.toolbox=false

For versioned builds (gen-dockerfile actions on a tagged version), the workflow is named Build and Push <version>, omits the schedule trigger, and pushes only that fixed version tag.

Hand-Crafted Workflow (.gitea/workflows/docker.yaml)

The legacy docker.yaml is hand-crafted; the reference copy lives in casjaysdevdocker/.github/example/ (the org-level repository). It differs from the generated workflow: uses catthehacker/ubuntu:act-latest container, tag-pinned actions (@v2/@v3/@v4), secrets.DOCKER_USERNAME + secrets.DOCKER_TOKEN, and the old steps.meta.outputs.* pattern. Do not use this as a template for new containers — use gen-dockerfile actions instead.


Package Manager Wrapper (pkmgr)

The pkmgr script in rootfs/usr/local/bin/pkmgr provides a unified interface:

pkmgr update          # Update package lists
pkmgr install <pkg>   # Install packages
pkmgr remove <pkg>    # Remove packages
pkmgr clean           # Clean package cache

Automatically detects and uses the appropriate package manager (apk, apt-get, dnf, pacman).


Development Workflow

Creating a New Container

# 1. Generate scaffolding
gen-dockerfile --dir ./myapp alpine

# 2. Customise .env.scripts (set ENV_REGISTRY_REPO, ENV_REGISTRY_ORG, ports, etc.)

# 3. Customise setup scripts in rootfs/root/docker/setup/

# 4. Generate an init.d runtime script
gen-dockerfile --startup 01-myapp.sh

# 5. Build and test
docker build -t myapp .
docker run -it myapp

# 6. Commit and push to trigger CI/CD

Updating an Existing Container

# Regenerate Dockerfile and related files from current .env.scripts
gen-dockerfile --dir ./myapp --update

# Regenerate workflow file only
gen-dockerfile --dir ./myapp actions

Adding a New Container from Scratch

  1. gen-dockerfile --dir /path/to/new-container --nogit alpine
  2. Edit .env.scripts — set ENV_REGISTRY_REPO, ENV_REGISTRY_PUSH, ENV_GIT_REPO_URL
  3. Generate the init.d service script: gen-dockerfile --startup 01-myapp.sh
  4. Add application-specific setup in rootfs/root/docker/setup/05-custom.sh

Project Categories

Web Servers

  • nginx, lighttpd, caddy

Databases

  • mysql, mariadb, postgres, mongodb, couchdb, redis, valkey, sqlite

Development Tools

  • code, neovim, vim, nodejs, python, php, deno, bun

Container Tools

  • docker, podman

Git Services

  • gitea, forgejo, soft-serve, opengist

Media Services

  • ampache, navidrome, mpd, ympd, icecast, youtube-dl

Networking

  • bind, traefik, tor, i2pd, ddns, ifconfig

Communication

  • mailman, postfix, gotify, ntfy, apprise

Cloud Services

  • nextcloud, wordpress

Utilities

  • tools, transmission, aria2, tftpd, ssl-ca, wttr, xfce4, webmin

Resources

  • Docker Hub: https://hub.docker.com/u/casjaysdevdocker
  • Git Repository: https://github.com/casjaysdevdocker
  • gen-dockerfile Tool: /usr/local/bin/gen-dockerfile
  • Template Source: /usr/local/share/CasjaysDev/scripts/templates/dockerfiles/ (installed) or $CASJAYSDEVDIR/templates/dockerfiles/ (dev override)
  • Example Project: casjaysdevdocker/.github/example/ (org-level repository — not present in individual container repos)