Add Docker support: Arch/Apache/PHP image, three volumes, docs
Adds a root Dockerfile (Arch Linux + Apache + PHP) and docker-compose.yml with separate cache/data/images volumes, so a project's own content, page cache, and future uploaded images stay out of paths a framework update would wipe. App/ is baked into the image but can be bind-mounted to override without a rebuild. Renames the Sass build-tool Dockerfile example in the styling doc to Dockerfile.sass to avoid colliding with the new app Dockerfile, adds a new /admin/docs/docker page (linked from the docs index and nav), and documents the reserved images/ directory in AGENTS.md and README. Also records the user's git/docker execution permission boundaries in CLAUDE.md for future sessions. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
graphify-out/
|
||||||
|
.claude/
|
||||||
|
public/cache/*
|
||||||
|
!public/cache/.gitkeep
|
||||||
|
data/*.sqlite*
|
||||||
|
images/*
|
||||||
|
!images/.gitkeep
|
||||||
|
docker-compose.yml
|
||||||
|
Dockerfile
|
||||||
|
Dockerfile.sass
|
||||||
@@ -7,3 +7,5 @@
|
|||||||
/data/*.sqlite-shm
|
/data/*.sqlite-shm
|
||||||
.claude/
|
.claude/
|
||||||
/graphify-out/
|
/graphify-out/
|
||||||
|
/images/*
|
||||||
|
!/images/.gitkeep
|
||||||
|
|||||||
@@ -246,6 +246,19 @@ index below) before `App/migrations/` (project schema) — see the
|
|||||||
project adds still defaults to no `migrations_dir` at all unless it sets
|
project adds still defaults to no `migrations_dir` at all unless it sets
|
||||||
one; the two-root default is specific to `default`.
|
one; the two-root default is specific to `default`.
|
||||||
|
|
||||||
|
A sibling top-level `images/` directory (same gitignore-wholesale-with-
|
||||||
|
`.gitkeep` treatment as `public/cache/`) exists as **reserved scaffolding
|
||||||
|
for a future image-upload feature — no config key, no upload code, nothing
|
||||||
|
reads or writes it yet.** It's deliberately a separate directory from
|
||||||
|
`data/`, not nested under it, even though both are "project data that
|
||||||
|
survives a framework update": a project may run MySQL, or no database at
|
||||||
|
all, so coupling image storage to the SQLite volume would be wrong. See
|
||||||
|
`novaconium/pages/admin/docs/docker/index.twig` for how the Docker Compose
|
||||||
|
setup gives it its own volume. Don't add an `images_dir` config key until
|
||||||
|
an actual feature reads it — an unused key would misrepresent the
|
||||||
|
framework's state, the same reasoning that keeps every other config key
|
||||||
|
tied to real consuming code.
|
||||||
|
|
||||||
`Lib\Session` (`novaconium/lib/Session.php`) is a thin wrapper around
|
`Lib\Session` (`novaconium/lib/Session.php`) is a thin wrapper around
|
||||||
native PHP sessions (`session_start()`/`$_SESSION`, not a custom store),
|
native PHP sessions (`session_start()`/`$_SESSION`, not a custom store),
|
||||||
all-static and lazy-start like `Lib\Csrf` — nothing calls `session_start()`
|
all-static and lazy-start like `Lib\Csrf` — nothing calls `session_start()`
|
||||||
|
|||||||
@@ -1 +1,6 @@
|
|||||||
|
# Agent permissions
|
||||||
|
|
||||||
|
- Only run `git` commands with the user's explicit permission for that specific command/action.
|
||||||
|
- Never run `docker` commands (build, compose up, run, etc.) — leave all Docker execution to the user.
|
||||||
|
|
||||||
@AGENTS.md
|
@AGENTS.md
|
||||||
|
|||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
# Arch Linux + Apache + PHP image for running novaconium in production.
|
||||||
|
# See /admin/docs/docker for volumes, overriding App/ without a rebuild,
|
||||||
|
# and optional MySQL wiring.
|
||||||
|
FROM archlinux:base
|
||||||
|
|
||||||
|
RUN pacman -Syu --noconfirm --needed apache php php-apache sqlite \
|
||||||
|
&& pacman -Scc --noconfirm
|
||||||
|
|
||||||
|
# php-apache on Arch is built against mpm_prefork, not httpd's default
|
||||||
|
# mpm_event — swap MPMs, enable mod_rewrite, point DocumentRoot at
|
||||||
|
# public/, and wire in mod_php.
|
||||||
|
RUN sed -i \
|
||||||
|
-e 's/^LoadModule mpm_event_module/#LoadModule mpm_event_module/' \
|
||||||
|
-e 's/^#LoadModule mpm_prefork_module/LoadModule mpm_prefork_module/' \
|
||||||
|
-e '/^#LoadModule rewrite_module/s/^#//' \
|
||||||
|
-e 's#DocumentRoot "/srv/http"#DocumentRoot "/var/www/html/public"#' \
|
||||||
|
-e 's#<Directory "/srv/http">#<Directory "/var/www/html/public">#' \
|
||||||
|
-e 's/^AllowOverride None/AllowOverride All/' \
|
||||||
|
/etc/httpd/conf/httpd.conf \
|
||||||
|
&& printf '\nLoadModule php_module modules/libphp.so\nAddHandler php-script .php\nDirectoryIndex index.php index.html\nServerName localhost\n' \
|
||||||
|
>> /etc/httpd/conf/httpd.conf \
|
||||||
|
&& sed -i \
|
||||||
|
-e 's/^;extension=pdo_sqlite/extension=pdo_sqlite/' \
|
||||||
|
-e 's/^;extension=pdo_mysql/extension=pdo_mysql/' \
|
||||||
|
/etc/php/php.ini
|
||||||
|
|
||||||
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
|
COPY novaconium/ ./novaconium/
|
||||||
|
COPY public/ ./public/
|
||||||
|
COPY App/ ./App/
|
||||||
|
|
||||||
|
# Runtime-writable paths not covered by named volumes in docker-compose.yml.
|
||||||
|
RUN mkdir -p public/cache data images \
|
||||||
|
&& touch novaconium/contact-log.txt \
|
||||||
|
&& chown -R http:http public/cache data images App novaconium/contact-log.txt
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
CMD ["httpd", "-D", "FOREGROUND"]
|
||||||
@@ -43,6 +43,14 @@ Visit `http://127.0.0.1:8000/` for the static home page, then click around — `
|
|||||||
|
|
||||||
Point the vhost's document root at `public/`, make sure `mod_rewrite` is enabled and `AllowOverride All` is set for that directory so `public/.htaccess` takes effect, and it just works — no build step required.
|
Point the vhost's document root at `public/`, make sure `mod_rewrite` is enabled and `AllowOverride All` is set for that directory so `public/.htaccess` takes effect, and it just works — no build step required.
|
||||||
|
|
||||||
|
### Run it in Docker
|
||||||
|
|
||||||
|
```
|
||||||
|
docker compose up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
Builds an Arch Linux-based Apache/PHP image and serves the site at `http://localhost:8080/`. Three named volumes (`cache`, `data`, `images`) keep the page cache, SQLite database, and a reserved-for-later images directory out of paths the "Updating the framework" workflow below would wipe; `App/` is baked into the image but can be bind-mounted for edits without a rebuild. See [Docker](http://127.0.0.1:8000/admin/docs/docker) for details.
|
||||||
|
|
||||||
### Starting a new project
|
### Starting a new project
|
||||||
|
|
||||||
Clone this repo and drop its Git history — no Composer scaffold or installer:
|
Clone this repo and drop its Git history — no Composer scaffold or installer:
|
||||||
@@ -86,6 +94,7 @@ php novaconium/bin/create-static-page.php blog/my-new-post
|
|||||||
The full framework documentation — routing, sidecars, libraries, database, session, content index, XML sitemap, RSS feeds, layouts, static caching, SEO, Matomo analytics, admin authentication, draft pages, styling, project layout, and third-party notices — lives at `/admin/docs` on any running instance (so it travels with the code, no internet connection needed). Highlights:
|
The full framework documentation — routing, sidecars, libraries, database, session, content index, XML sitemap, RSS feeds, layouts, static caching, SEO, Matomo analytics, admin authentication, draft pages, styling, project layout, and third-party notices — lives at `/admin/docs` on any running instance (so it travels with the code, no internet connection needed). Highlights:
|
||||||
|
|
||||||
- [Getting started](http://127.0.0.1:8000/admin/docs/getting-started)
|
- [Getting started](http://127.0.0.1:8000/admin/docs/getting-started)
|
||||||
|
- [Docker](http://127.0.0.1:8000/admin/docs/docker)
|
||||||
- [Routing](http://127.0.0.1:8000/admin/docs/routing)
|
- [Routing](http://127.0.0.1:8000/admin/docs/routing)
|
||||||
- [Sidecars](http://127.0.0.1:8000/admin/docs/sidecars)
|
- [Sidecars](http://127.0.0.1:8000/admin/docs/sidecars)
|
||||||
- [Libraries](http://127.0.0.1:8000/admin/docs/libraries)
|
- [Libraries](http://127.0.0.1:8000/admin/docs/libraries)
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
services:
|
||||||
|
web:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- "8080:80"
|
||||||
|
volumes:
|
||||||
|
- cache:/var/www/html/public/cache
|
||||||
|
- data:/var/www/html/data
|
||||||
|
- images:/var/www/html/images
|
||||||
|
# Uncomment to override the baked-in App/ with a host copy, no rebuild:
|
||||||
|
# - ./App:/var/www/html/App
|
||||||
|
|
||||||
|
# Optional — only needed if App/config.php adds a db_connections entry
|
||||||
|
# with driver: mysql. See /admin/docs/database.
|
||||||
|
# db:
|
||||||
|
# image: mysql:8
|
||||||
|
# environment:
|
||||||
|
# MYSQL_DATABASE: novaconium
|
||||||
|
# MYSQL_USER: novaconium
|
||||||
|
# MYSQL_PASSWORD: change-me
|
||||||
|
# MYSQL_ROOT_PASSWORD: change-me
|
||||||
|
# volumes:
|
||||||
|
# - mysql-data:/var/lib/mysql
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
cache:
|
||||||
|
data:
|
||||||
|
images:
|
||||||
|
# mysql-data:
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li><a class="icon-link" href="/admin/docs">{{ icons.book() }}Overview</a></li>
|
<li><a class="icon-link" href="/admin/docs">{{ icons.book() }}Overview</a></li>
|
||||||
<li><a class="icon-link" href="/admin/docs/getting-started">{{ icons.book() }}Getting started</a></li>
|
<li><a class="icon-link" href="/admin/docs/getting-started">{{ icons.book() }}Getting started</a></li>
|
||||||
|
<li><a class="icon-link" href="/admin/docs/docker">{{ icons.book() }}Docker</a></li>
|
||||||
<li><a class="icon-link" href="/admin/docs/routing">{{ icons.link() }}Routing</a></li>
|
<li><a class="icon-link" href="/admin/docs/routing">{{ icons.link() }}Routing</a></li>
|
||||||
<li><a class="icon-link" href="/admin/docs/sidecars">{{ icons.book() }}Sidecars</a></li>
|
<li><a class="icon-link" href="/admin/docs/sidecars">{{ icons.book() }}Sidecars</a></li>
|
||||||
<li><a class="icon-link" href="/admin/docs/forms">{{ icons.email() }}Forms</a></li>
|
<li><a class="icon-link" href="/admin/docs/forms">{{ icons.email() }}Forms</a></li>
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
{% extends 'admin/docs/_layout/layout.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Docker{% endblock %}
|
||||||
|
|
||||||
|
{% block description %}Running novaconium in a container: the Apache/PHP image, its three volumes, and overriding App/ without a rebuild.{% endblock %}
|
||||||
|
|
||||||
|
{% block robots %}noindex, nofollow{% endblock %}
|
||||||
|
|
||||||
|
{% block docs_content %}
|
||||||
|
<h1>Docker</h1>
|
||||||
|
|
||||||
|
<p>The root <code>Dockerfile</code> builds an Apache + PHP image on an <a href="https://archlinux.org/">Arch Linux</a> base, with <code>mod_rewrite</code>, <code>AllowOverride All</code>, and <code>pdo_sqlite</code>/<code>pdo_mysql</code> already enabled — nothing else in <a href="/admin/docs/getting-started">Getting started</a>'s "Deploy on Apache" section needs configuring by hand. <code>docker-compose.yml</code> wires it up with three named volumes so a project's own content, cache, and (future) images never live inside a path that gets wiped by the <a href="/admin/docs/getting-started">"Updating the framework"</a> workflow.</p>
|
||||||
|
|
||||||
|
<pre><code>docker compose up --build</code></pre>
|
||||||
|
|
||||||
|
<p>Visit <code>http://localhost:8080/</code>.</p>
|
||||||
|
|
||||||
|
<h2>The volumes</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><code>cache</code> → <code>public/cache/</code> — the static HTML page cache (see <a href="/admin/docs/caching">Static caching</a>). Disposable; clear it from inside the container with <code>docker compose exec web php novaconium/bin/clear-cache.php</code>.</li>
|
||||||
|
<li><code>data</code> → <code>data/</code> — holds <code>data/novaconium.sqlite</code> if <a href="/admin/docs/database">Database</a>-backed features (admin auth, content index) are enabled. Persists across <code>docker compose down</code>/<code>up</code> as long as you don't pass <code>-v</code>.</li>
|
||||||
|
<li><code>images</code> → <code>images/</code> — a reserved, empty directory for a future image-upload feature. Nothing in the framework reads from or writes to it yet — there's no config key or upload code — it's scaffolding only, kept on its own volume deliberately, since a project might use MySQL or no database at all and shouldn't have image storage coupled to the SQLite volume.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p><code>App/</code> itself is <strong>not</strong> a named volume — it's baked into the image with <code>COPY</code> at build time, so <code>docker compose up --build</code> alone produces a working site with no extra steps. A named volume seeded from <code>COPY App/</code> would only populate once, on first container creation, and would silently go stale on every later rebuild. To edit content without rebuilding, uncomment the bind mount in <code>docker-compose.yml</code>:</p>
|
||||||
|
|
||||||
|
<pre><code>volumes:
|
||||||
|
- cache:/var/www/html/public/cache
|
||||||
|
- data:/var/www/html/data
|
||||||
|
- images:/var/www/html/images
|
||||||
|
- ./App:/var/www/html/App</code></pre>
|
||||||
|
|
||||||
|
<p>A bind mount at the same path as a <code>COPY</code>'d directory shadows the image layer at container start, so a host-side edit under <code>App/pages/</code> shows up after <code>docker compose restart web</code> — no rebuild, no custom entrypoint logic.</p>
|
||||||
|
|
||||||
|
<h2>MySQL</h2>
|
||||||
|
|
||||||
|
<p><code>Lib\Db</code> supports MySQL alongside or instead of SQLite (see <a href="/admin/docs/database">Database</a>). <code>docker-compose.yml</code> has a commented-out <code>db</code> service and <code>mysql-data</code> volume — uncomment both, add a <code>db_connections</code> entry with <code>driver: mysql</code> and matching credentials in <code>App/config.php</code>, and the app container can reach it at hostname <code>db</code>.</p>
|
||||||
|
|
||||||
|
<h2>Arch-specific notes</h2>
|
||||||
|
|
||||||
|
<p>Arch's <code>php-apache</code> package is built against <code>mpm_prefork</code>, not <code>httpd</code>'s default <code>mpm_event</code> — the Dockerfile swaps MPMs as part of the build. The Apache/PHP worker user on Arch is <code>http</code>, not Debian's <code>www-data</code>; the Dockerfile <code>chown</code>s the cache/data/images/App directories and <code>novaconium/contact-log.txt</code> to <code>http:http</code> at build time so a freshly created named volume (which inherits the image mountpoint's ownership) is writable immediately. If you swap a named volume for a bind mount pointing at a host directory with different ownership, that automatic chown doesn't apply — you may need to adjust permissions on the host side.</p>
|
||||||
|
|
||||||
|
<p>This is a separate Dockerfile from the one-off Dart Sass build tool described in <a href="/admin/docs/styling">Styling</a> — that one lives at <code>Dockerfile.sass</code>, a Debian-based image whose only job is running the <code>sass</code> CLI, not serving the app.</p>
|
||||||
|
{% endblock %}
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><a class="icon-link" href="/admin/docs/getting-started">{{ icons.book() }}Getting started</a> — requirements, running locally, deploying on Apache.</li>
|
<li><a class="icon-link" href="/admin/docs/getting-started">{{ icons.book() }}Getting started</a> — requirements, running locally, deploying on Apache.</li>
|
||||||
|
<li><a class="icon-link" href="/admin/docs/docker">{{ icons.book() }}Docker</a> — the Apache/PHP image, its three volumes, and overriding <code>App/</code> without a rebuild.</li>
|
||||||
<li><a class="icon-link" href="/admin/docs/routing">{{ icons.link() }}Routing</a> — how a URL maps to a directory under <code>App/pages/</code>.</li>
|
<li><a class="icon-link" href="/admin/docs/routing">{{ icons.link() }}Routing</a> — how a URL maps to a directory under <code>App/pages/</code>.</li>
|
||||||
<li><a class="icon-link" href="/admin/docs/sidecars">{{ icons.book() }}Sidecars</a> — where your PHP logic goes.</li>
|
<li><a class="icon-link" href="/admin/docs/sidecars">{{ icons.book() }}Sidecars</a> — where your PHP logic goes.</li>
|
||||||
<li><a class="icon-link" href="/admin/docs/forms">{{ icons.email() }}Forms</a> — building a custom form with a sidecar, using the novaconium validation/spam libraries.</li>
|
<li><a class="icon-link" href="/admin/docs/forms">{{ icons.email() }}Forms</a> — building a custom form with a sidecar, using the novaconium validation/spam libraries.</li>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
<p>There's no PHP-based Sass compiler in this project (PHP options like <code>scssphp</code> only understand SCSS syntax) — this is a manual/CI build step, not something the app does at runtime.</p>
|
<p>There's no PHP-based Sass compiler in this project (PHP options like <code>scssphp</code> only understand SCSS syntax) — this is a manual/CI build step, not something the app does at runtime.</p>
|
||||||
|
|
||||||
<p>Don't have Dart Sass installed? Run it via Docker instead — copy-paste this Dockerfile, which installs the same official standalone Dart Sass release used in this environment (<code>1.101.0</code>, via <code>pacman -S dart-sass</code> on Arch), not the npm-wrapped build:</p>
|
<p>Don't have Dart Sass installed? Run it via Docker instead — copy-paste this into a file named <code>Dockerfile.sass</code> (the project's own root <code>Dockerfile</code> is the app container, see <a href="/admin/docs/docker">Docker</a> — this is a separate, one-off build tool, so it gets its own filename), which installs the same official standalone Dart Sass release used in this environment (<code>1.101.0</code>, via <code>pacman -S dart-sass</code> on Arch), not the npm-wrapped build:</p>
|
||||||
|
|
||||||
<pre><code>FROM debian:bookworm-slim
|
<pre><code>FROM debian:bookworm-slim
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ ENTRYPOINT ["sass"]</code></pre>
|
|||||||
|
|
||||||
<p>Build it once, then run it the same way you'd run the local <code>sass</code> CLI (skip the leading <code>sass</code> in the command — the image's <code>ENTRYPOINT</code> already supplies it):</p>
|
<p>Build it once, then run it the same way you'd run the local <code>sass</code> CLI (skip the leading <code>sass</code> in the command — the image's <code>ENTRYPOINT</code> already supplies it):</p>
|
||||||
|
|
||||||
<pre><code>docker build -t novaconium-sass -f Dockerfile .
|
<pre><code>docker build -t novaconium-sass -f Dockerfile.sass .
|
||||||
docker run --rm -v "$(pwd):/usr/src/app" -w /usr/src/app novaconium-sass \
|
docker run --rm -v "$(pwd):/usr/src/app" -w /usr/src/app novaconium-sass \
|
||||||
--load-path=App/sass --load-path=novaconium/sass/defaults novaconium/sass/main.sass public/css/main.css</code></pre>
|
--load-path=App/sass --load-path=novaconium/sass/defaults novaconium/sass/main.sass public/css/main.css</code></pre>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user