Documents the canonical repo template spec (CLAUDE.md, copied from the project-wide TEMPLATE.md) and the concrete migration plan for the ampache service stack: Apache + PHP-FPM (php84) + MariaDB-server bundled with the Ampache 7.9.3 web app prebuilt zip from upstream.
15 KiB
ampache migration plan
Service intent
Self-hosted music streaming server. Single Alpine-based Docker image bundling Apache (httpd) + PHP-FPM (php84) + MariaDB-server + the Ampache web app at /usr/local/share/ampache. Apache serves the app's public/ subfolder (the canonical Ampache web root since v5+). Container exposes :80; first run takes the user to install.php which provisions the database and creates an admin account. Volumes: /config (user-editable settings: ampache config, httpd conf snippets, php-fpm pool, my.cnf, secure/auth) and /data (mariadb datadir, logs, uploaded media library if mounted there).
Service stack
- Web server:
apache2(Alpine) ->/usr/sbin/httpd, main config/etc/apache2/httpd.conf, vhost include in/etc/apache2/conf.d/*.conf. Uses event MPM, mod_rewrite + AllowOverride All for the Ampache.htaccess. - App runtime:
php84-fpm(Alpine) ->/usr/sbin/php-fpm84, conf/etc/php84/php-fpm.conf+ pool/etc/php84/php-fpm.d/www.conf. Apache talks to it viaproxy_fcgiover a unix socket/run/php-fpm/php-fpm.sock(no exposed TCP). - Database:
mariadb+mariadb-client(Alpine) ->/usr/bin/mariadbd, datadir/data/db/mariadb, socket/run/mysqld/mysqld.sock. Started by a separate09-mariadb.shso it's up before ampache is rendered. - Application: Ampache 7.9.3 (
ampache-7.9.3_all_php8.4.zipfrom upstream GitHub releases — pre-built, vendor/ + node_modules/ already populated, no composer/npm needed at build time). Installed at/usr/local/share/ampache; ApacheDocumentRootis/usr/local/share/ampache/public.
Packages (PACK_LIST / ENV_PACKAGES)
Trimmed from the original kitchen-sink list in the existing Dockerfile. Each package is on pkgs.alpinelinux.org (verified for the edge branch).
System glue:
bash,tini,curl,wget,unzip,tzdata,ca-certificates,pwgen— entrypoint, fetching/extracting the ampache zip, password generation.tar,gzip— archive handling.
Apache:
apache2,apache2-ctl,apache2-utils— server binary, control script, htpasswd/etc.apache2-ssl— mod_ssl (TLS support; off by default but available).apache2-proxy— mod_proxy + mod_proxy_fcgi (required to forward PHP requests to php-fpm).apache2-http2— http/2 support.apache2-brotli— mod_brotli compression.apache2-icons,apache2-error— icons + multilingual error pages.
(Dropped from prior list: apache2-lua apache2-ldap apache2-webdav apache2-mod-wsgi apache-mod-fcgid apache2-proxy-html — none are needed for ampache and apache2-proxy is the canonical fcgi backend, not mod_fcgid.)
PHP 8.4 — only the modules Ampache actually requires per upstream docs (PDO, PDO_MYSQL, hash, session, intl, json, curl, simplexml + optional gd, ldap, zip), plus FPM and the bare runtime:
php84php84-fpmphp84-commonphp84-ctypephp84-pdophp84-pdo_mysqlphp84-mysqliphp84-mysqlndphp84-sessionphp84-intlphp84-curlphp84-simplexmlphp84-xmlphp84-xmlreaderphp84-xmlwriterphp84-domphp84-mbstringphp84-iconvphp84-tokenizerphp84-fileinfophp84-opensslphp84-pharphp84-gdphp84-zipphp84-bz2php84-gmpphp84-exifphp84-opcachephp84-pecl-redis
(Dropped from prior list: dba, dev, doc, embed, enchant, ffi, ftp, gettext, imap, ldap (kept off — not needed by ampache out-of-box), litespeed, odbc, pcntl, pdo_dblib, pdo_odbc, pdo_pgsql, pdo_sqlite, pear, pgsql, phpdbg, posix, pspell, shmop, snmp, soap, sockets, sodium, sqlite3, sysvmsg, sysvsem, sysvshm, tidy, xsl, pecl-memcached, pecl-mcrypt, pecl-mongodb, calendar, cgi, bcmath. Kept what either a stock Ampache install touches or is part of ampache's bundled vendor extensions. composer is dropped — we use the prebuilt zip, no install step needed.)
MariaDB:
mariadb— server (/usr/bin/mariadbd).mariadb-client—mariadbCLI client used by the post-execute initdb step.mariadb-server-utils—mysql_install_db,mariadb-admin,mariadb-secure-installation.
Configs to ship in rootfs/tmp/etc/
Wipe-and-replace at build time (per template §4). All paths under rootfs/tmp/etc/.
apache2/httpd.conf— minimal Alpine apache main config: load only the modules we need (mpm_event, mime, dir, alias, authz_core, authz_host, autoindex, deflate, brotli, expires, headers, log_config, mime_magic, negotiation, proxy, proxy_fcgi, rewrite, setenvif, ssl, status, unixd, http2). User/Groupapache:apache. ServerRoot/var/www. Logs to/data/logs/apache2/{access,error}.log. PidFile/run/apache2/httpd.pid. ErrorLog/LogLevel sensible defaults. Final line:IncludeOptional /etc/apache2/conf.d/*.confandIncludeOptional /config/apache2/vhosts.d/*.conf(optional, so empty dir doesn't crash).apache2/conf.d/ampache.conf— vhost:<VirtualHost *:80>withDocumentRoot /usr/local/share/ampache/public,<Directory>block (Options FollowSymLinks,AllowOverride All,Require all granted),ProxyPassMatch ^/(.+\.php(/.*)?)$ unix:/run/php-fpm/php-fpm.sock|fcgi://localhost/usr/local/share/ampache/public/$1,DirectoryIndex index.php index.html. SetsSetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1(per upstream Apache vhost, needed for ampache API auth headers). ErrorLog/CustomLog under/data/logs/apache2/.apache2/conf.d/mpm.conf— switch to mpm_event explicitly + sane worker tunings.php84/php.ini— production-tuned:memory_limit = 512M,upload_max_filesize = 256M,post_max_size = 256M,max_execution_time = 300,date.timezone = ${TZ},expose_php = Off,cgi.fix_pathinfo = 0,opcache.enable = 1,opcache.memory_consumption = 256,opcache.max_accelerated_files = 20000,session.save_path = /tmp/php-sessions.php84/php-fpm.conf— global:pid = /run/php-fpm/php-fpm.pid,error_log = /data/logs/php-fpm/error.log,daemonize = no(we run under our own supervisor),include=/etc/php84/php-fpm.d/*.conf.php84/php-fpm.d/www.conf— pool[www],user = apache,group = apache,listen = /run/php-fpm/php-fpm.sock,listen.owner = apache,listen.group = apache,listen.mode = 0660,pm = dynamic,pm.max_children = 20,pm.start_servers = 4,pm.min_spare_servers = 2,pm.max_spare_servers = 8,pm.max_requests = 500,clear_env = no.my.cnf.d/mariadb-server.cnf— server config:[mysqld]withdatadir = /data/db/mariadb,socket = /run/mysqld/mysqld.sock,bind-address = 127.0.0.1(DB only reachable inside container),port = 3306,character-set-server = utf8mb4,collation-server = utf8mb4_unicode_ci,max_allowed_packet = 64M,innodb_buffer_pool_size = 256M,log_error = /data/logs/mariadb/mariadb.err.log,pid-file = /run/mysqld/mariadb.pid.[client]socket = /run/mysqld/mysqld.sock.[mysqld_safe]matching log path.ampache/ampache.cfg.php.dist— empty placeholder (or a copy of the ampache-shipped dist file). Realampache.cfg.phpis generated byinstall.phpon first web visit.
/config// layout (user-editable)
The framework's __initialize_system_etc symlinks every file under /config/<svc>/ back to its /etc/<svc>/ peer. So our /config/ seed (via template-files/config/) mirrors /etc/ with the same paths:
/config/apache2/httpd.conf-> symlinked to/etc/apache2/httpd.conf/config/apache2/conf.d/ampache.conf-> symlinked to/etc/apache2/conf.d/ampache.conf/config/apache2/conf.d/mpm.conf-> symlinked to/etc/apache2/conf.d/mpm.conf/config/apache2/vhosts.d/*.conf-> picked up by theIncludeOptionalline for user-supplied vhosts/config/php84/php.ini->/etc/php84/php.ini/config/php84/php-fpm.conf->/etc/php84/php-fpm.conf/config/php84/php-fpm.d/www.conf->/etc/php84/php-fpm.d/www.conf/config/my.cnf.d/mariadb-server.cnf->/etc/my.cnf.d/mariadb-server.cnf/config/ampache/ampache.cfg.php-> bind-mounted into/usr/local/share/ampache/config/ampache.cfg.php(created post-install byinstall.php; we symlink the live file out to/config/ampache/after install completes so it survives container recreation)./config/secure/auth/{root,user}/{ampache,mariadb}_{name,pass}-> generated/used by the framework/config/env/{ampache,mariadb}.sh-> per-service env overrides
ADDITIONAL_CONFIG_DIRS for ampache will be /config/apache2 /config/php84 /config/my.cnf.d /config/ampache so each one runs through __initialize_system_etc.
init.d/99-ampache.sh (and 09-mariadb.sh)
Two init.d scripts. MariaDB starts first (09-), Apache+PHP-FPM start under ampache (99-).
rootfs/usr/local/etc/docker/init.d/09-mariadb.sh — copy of mariadb repo's 09-mariadb.sh with these knobs:
SERVICE_NAME="mariadb",EXEC_CMD_BIN='mariadbd',EXEC_CMD_ARGS='--user=$SERVICE_USER --datadir=$DATABASE_DIR --socket=/run/mysqld/mysqld.sock'SERVICE_USER="mysql",SERVICE_GROUP="mysql"IS_DATABASE_SERVICE="yes",DATABASE_SERVICE_TYPE="mariadb"__run_pre_execute_checks_local: bootstrap datadir if missing (mysql_install_db --datadir=$DATABASE_DIR --user=mysql).__post_execute_local: when first run, create theampachedatabase +ampacheuser + grant; write password to/config/secure/auth/user/ampache_db_pass.
rootfs/usr/local/etc/docker/init.d/99-ampache.sh — based on nginx's 99-nginx.sh structure:
SERVICE_NAME="ampache",SERVICE_USER="apache",SERVICE_GROUP="apache"EXEC_CMD_BIN='httpd',EXEC_CMD_ARGS='-D FOREGROUND -f /etc/apache2/httpd.conf'EXEC_PRE_SCRIPT='/usr/sbin/php-fpm84'— start php-fpm as the pre-script (it reads/etc/php84/php-fpm.confwithdaemonize=nobut we run it before httpd; the framework's EXEC_PRE_SCRIPT pattern handles this).IS_WEB_SERVER="yes",USES_DATABASE_SERVICE="yes",DATABASE_SERVICE_TYPE="mariadb"WWW_ROOT_DIR="/usr/local/share/ampache/public",ETC_DIR="/etc/apache2",CONF_DIR="/config/apache2"ADDITIONAL_CONFIG_DIRS="/config/php84 /config/my.cnf.d /config/ampache"SERVICE_PORT="80"__execute_prerun_local: ensure runtime dirs (/run/apache2,/run/php-fpm,/run/mysqld,/tmp/php-sessions,/data/logs/{apache2,php-fpm,mariadb,ampache}) exist with right ownership; chown/usr/local/share/ampache/{config,channel,rest,play}toapache:apachefor the install.php writes; symlink/usr/local/share/ampache/config/ampache.cfg.php->/config/ampache/ampache.cfg.phpif not present.__pre_execute_local: wait for mariadb socket to appear (/run/mysqld/mysqld.sock) up to 30s before starting apache.__update_conf_files_local: replaceREPLACE_TZtoken in/etc/php84/php.iniwith$TZ. (No tokens in apache or my.cnf — paths are baked in.)
05-custom.sh additions
Replace the current placeholder content (which only mkdirs and creates an empty webapp dir) with:
- Wipe distro-default
/etc/{apache2,php84,my.cnf.d}/*(drop conf.d/{default,info,languages,mpm,userdir}.conf, ssl.conf etc.) so only our shipped files remain aftercp -Rf /tmp/etc/....for d in apache2 php84 my.cnf.d; do [ -d /tmp/etc/$d ] || continue rm -Rf /etc/$d/* cp -Rf /tmp/etc/$d/. /etc/$d/ done - Fetch + install Ampache 7.9.3 prebuilt zip:
AMPACHE_VERSION="7.9.3" AMPACHE_URL="https://github.com/ampache/ampache/releases/download/${AMPACHE_VERSION}/ampache-${AMPACHE_VERSION}_all_php8.4.zip" mkdir -p /usr/local/share/ampache cd /tmp wget -q -O /tmp/ampache.zip "$AMPACHE_URL" unzip -q /tmp/ampache.zip -d /usr/local/share/ampache rm -f /tmp/ampache.zip chown -R apache:apache /usr/local/share/ampache # ensure config dir exists for install.php to drop ampache.cfg.php mkdir -p /usr/local/share/ampache/config chown apache:apache /usr/local/share/ampache/config - Create runtime dirs needed by apache/php-fpm/mariadb so the first start doesn't trip on missing parents:
mkdir -p /run/apache2 /run/php-fpm /run/mysqld /var/log/apache2 /tmp/php-sessions - Stage seed
template-files/config/ampache/with an empty.gitkeepso__initialize_config_dircreates/config/ampache/on first run (the realampache.cfg.phpis written by install.php).
04-users.sh additions
The mariadb Alpine package creates the mysql user automatically; the apache2 package creates apache. So 04-users.sh stays mostly empty — but add a defensive addgroup -S apache 2>/dev/null; adduser -S -G apache -h /var/www -s /sbin/nologin apache 2>/dev/null block in case package ordering doesn't guarantee them.
02-packages.sh additions
Empty (no per-package compile or pip step needed; the prebuilt ampache zip has all PHP deps inside vendor/).
Dockerfile changes
- Update
BUILD_DATEto202605091200(today, 2026-05-09 12:00). - Replace
PACK_LISTwith the trimmed list above. - Keep everything else (multi-stage, scratch final, ARGs, ENVs, volumes, healthcheck).
- No structural changes — the template's RUN steps already invoke 00–07 setup scripts in order.
.env.scripts changes
- Sync
ENV_PACKAGESto match the newPACK_LIST(single space separated, no double spaces). - Leave
SERVICE_PORT="80",EXPOSE_PORTS="",PHP_VERSION="php84".
README updates
Document the first-run workflow: visit http://localhost:8080/ → ampache installer → fill in DB host 127.0.0.1, user ampache, password from /config/secure/auth/user/ampache_db_pass (or read from docker exec), DB name ampache (already created by post-execute), web admin → enter desired admin user/email/pass → done. Note the volumes (/config, /data) and the music library mount pattern (-v /path/to/music:/media:ro).
Verification (success criteria)
cd /root/Projects/github/casjaysdevdocker/ampache && rm -f .build_failed && buildx run Dockerfilesucceeds for bothlinux/amd64andlinux/arm64. Single retry permitted on transient network errors.docker run -d --rm --name test-ampache -p 18080:80 docker.io/casjaysdevdocker/ampache:latestboots; after ~60s,docker logs test-ampache | tail -50shows no fatal errors and shows mariadb + php-fpm + httpd all started (look for "ready for connections", "fpm is running", "Apache/2... configured -- resuming normal operations").curl -fsS -o /dev/null -w '%{http_code}' http://localhost:18080/returns 200 or 302 (the install.php redirect).docker exec test-ampache ls /config/ampache/ /config/apache2/ /config/php84/ /data/db/mariadb/ /usr/local/share/ampache/public/index.php— every path exists.docker exec test-ampache mariadb -u root -e 'SHOW DATABASES;'listsampache.docker stop test-ampache.
Rollback
If anything in this PLAN.md proves wrong, the existing files are recoverable from git (git checkout -- rootfs/). New files (init.d, tmp/etc) can be removed cleanly because they didn't exist before.