build: M1 Dockerized development stack with PHP skeleton, config/logging, Tailwind assets, and wrappers
Implements MeshVault M1 (issues #6–11): - Four-service Docker Compose stack (nginx, PHP-FPM 8.3, PostgreSQL 17, Node assets) - Slim 4 + PHP-DI routing with escaped PHP views and production/dev error handling - Immutable validated AppConfig with redacted diagnostics - JSON stdout Monolog logging with job-scoped context - Tailwind CSS v4 deterministic locked builds with template scanning - POSIX and PowerShell dev wrappers (up/down/test/lint/assets/watch) - Startup config validation fails before FPM serves as unprivileged www-data - migrate/seed explicitly unavailable until M2 (#12); #10 remains open Verified via clean-start acceptance on remote host (keith@192.168.2.23).
This commit is contained in:
1 parent
c2747a5762
commit
d3e8cf8fae
39 files changed
+5291
-38
No files matched your search
+113
-6
@@ -1,7 +1,114 @@
|
||||
# Container configuration
|
||||
# Development containers (M1)
|
||||
|
||||
M1 issue #6 adds nginx, PHP-FPM, PostgreSQL and asset build services. There is no
|
||||
Compose file yet. Keep database data, managed library files and uploaded media
|
||||
persistent and outside the application source. Mount import sources read-only
|
||||
where possible. Node is asset tooling in MVP, not a required mesh-processing
|
||||
runtime. Never bake credentials or user collections into images.
|
||||
Four-service Docker Compose stack: nginx, unprivileged PHP-FPM, PostgreSQL and
|
||||
an assets container for Tailwind CSS builds. The application layer (Slim 4,
|
||||
PHP-DI, Monolog) is wired, but there is no catalog schema, migration runner or
|
||||
asset pipeline beyond Tailwind. Do not expose this stack to the Internet.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker Engine/Desktop with Linux containers and Docker Compose v2+.
|
||||
- No host PHP, Composer or Node is required.
|
||||
|
||||
## First start
|
||||
|
||||
From the repository root, copy `.env.example` to `.env` and edit
|
||||
`MESHVAULT_DB_PASSWORD` to a private, nonempty value. Never commit `.env` or print
|
||||
expanded Compose configuration in shared logs; use `docker compose config --quiet`.
|
||||
|
||||
Create a **new dedicated** `library` directory (`mkdir library`). Do not use an
|
||||
existing collection. On Linux, give container UID/GID 33 ownership of that new
|
||||
empty directory, for example `sudo chown 33:33 ./library` and
|
||||
`sudo chmod 750 ./library`. Alternatively, after building, use the narrowly scoped
|
||||
one-off command below **only for this new empty directory**:
|
||||
|
||||
```sh
|
||||
docker compose build app
|
||||
docker compose run --rm --no-deps --user root app chown 33:33 /var/lib/meshvault/library
|
||||
docker compose config --quiet
|
||||
docker compose up -d --wait
|
||||
```
|
||||
|
||||
Also ensure `public/build` is writable by the assets container user (`node`, UID
|
||||
1000). On Linux, run:
|
||||
|
||||
```sh
|
||||
mkdir -p public/build
|
||||
chown 1000:1000 public/build
|
||||
```
|
||||
|
||||
Docker Desktop bind permissions differ; verify writability using the runtime
|
||||
identity rather than assuming host chmod works. The stack never automatically
|
||||
creates or changes ownership of a configured library directory.
|
||||
|
||||
## Task wrappers
|
||||
|
||||
Use `dev.sh` (POSIX) or `dev.ps1` (PowerShell) from the repository root. They are
|
||||
independent of your current working directory and propagate exit codes.
|
||||
|
||||
```sh
|
||||
./dev.sh up # build and start services
|
||||
./dev.sh down # stop services (volume removal is forbidden)
|
||||
./dev.sh test # run composer test inside the app container
|
||||
./dev.sh lint # run composer lint inside the app container
|
||||
./dev.sh assets # build minified CSS inside the assets container
|
||||
./dev.sh watch # start Tailwind watch inside the assets container
|
||||
./dev.sh migrate # unavailable until M2 (#12); exits nonzero
|
||||
./dev.sh seed # unavailable until M2 (#12); exits nonzero
|
||||
```
|
||||
|
||||
`down --volumes` and `down -v` are blocked by the wrapper. Never remove the named
|
||||
`pgdata` volume or the library bind.
|
||||
|
||||
## Asset builds
|
||||
|
||||
Assets are built inside the `assets` service using a locked `package-lock.json`.
|
||||
The generated `public/build/app.css` is served statically by nginx.
|
||||
|
||||
```sh
|
||||
./dev.sh assets
|
||||
```
|
||||
|
||||
Watch mode rebuilds on template changes:
|
||||
|
||||
```sh
|
||||
./dev.sh watch
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
Open http://127.0.0.1:8090/ for the landing page. `docker compose exec app id`
|
||||
should report UID/GID 33. Check a disposable library write without becoming root:
|
||||
|
||||
```sh
|
||||
docker compose exec app php -r '$p = getenv("MESHVAULT_LIBRARY_DIR") . "/write-check"; if (file_put_contents($p, "ok") === false) { exit(1); } unlink($p);'
|
||||
```
|
||||
|
||||
`down` preserves the named `pgdata` volume and library bind. Changing database
|
||||
credentials in `.env` does not change credentials in an initialized PostgreSQL
|
||||
volume. Back up data before upgrades.
|
||||
|
||||
## Settings and boundaries
|
||||
|
||||
`.env.example` documents every current setting: Compose project, HTTP host/port,
|
||||
DB name/user/password, host library path and absolute container library path.
|
||||
The container library path must stay outside `/var/www/html` and must not overlap
|
||||
system paths. The only required non-default value is the database password.
|
||||
The default project is `meshvault-m1`; HTTP binds localhost:8090. Only nginx has a
|
||||
published port. PostgreSQL readiness gates app startup. FPM itself runs as
|
||||
`www-data`, not just its workers. nginx mounts only `public/` and executes only
|
||||
`/index.php`; source, credentials and library are not web mounts.
|
||||
|
||||
Source mounts are explicit and read-only. No host `vendor` or `node_modules` is
|
||||
mounted; dependencies are installed inside images from lockfiles. No Docker socket
|
||||
is mounted. Startup configuration validation is provided by the entrypoint script.
|
||||
|
||||
Image branches: PHP 8.3 FPM Bookworm (security support to December 2027), PostgreSQL
|
||||
17 Bookworm (support to November 2029), nginx 1.30 Alpine (stable), Node 24
|
||||
Bookworm slim (LTS). Tags are versioned but move with patch updates, not digest
|
||||
locks. PHP 8.3 compatibility is retained.
|
||||
|
||||
Remote verification uses only `/home/keith/Projects/MeshVault` after confirming
|
||||
HOME, explicit project `meshvault-m1`, and an explicit source-only copy manifest.
|
||||
Access from another machine via `ssh -L 8090:127.0.0.1:8090 keith@192.168.2.23`.
|
||||
Never attach original collections or change unrelated services.
|
||||
@@ -0,0 +1,7 @@
|
||||
FROM node:24-bookworm-slim
|
||||
WORKDIR /workspace
|
||||
COPY --chown=node:node package.json package-lock.json ./
|
||||
RUN chown node:node /workspace
|
||||
USER node
|
||||
RUN npm ci --no-audit --no-fund
|
||||
CMD ["npm", "run", "build"]
|
||||
@@ -0,0 +1,28 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /var/www/html/public;
|
||||
index index.php;
|
||||
server_tokens off;
|
||||
autoindex off;
|
||||
|
||||
location ~ /\. {
|
||||
return 404;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
|
||||
# Only the front controller may execute; never arbitrary uploaded PHP.
|
||||
location = /index.php {
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/html/public/index.php;
|
||||
fastcgi_param HTTP_PROXY "";
|
||||
fastcgi_pass app:9000;
|
||||
}
|
||||
|
||||
location ~* \.php(?:/|$) {
|
||||
return 404;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
FROM php:8.3-fpm-bookworm
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends libonig-dev libxml2-dev unzip \
|
||||
&& docker-php-ext-install mbstring dom \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
|
||||
WORKDIR /var/www/html
|
||||
# Dependencies are Linux-native and rebuilt from the lock, never host-mounted.
|
||||
COPY composer.json composer.lock ./
|
||||
RUN composer install --no-interaction --prefer-dist --no-progress
|
||||
COPY bin ./bin
|
||||
COPY docker/php/entrypoint.sh /usr/local/bin/meshvault-entrypoint
|
||||
COPY docker/php/zz-meshvault.conf /usr/local/etc/php-fpm.d/zz-meshvault.conf
|
||||
RUN chmod 755 /usr/local/bin/meshvault-entrypoint
|
||||
RUN mkdir -p /var/www/html/.phpunit.cache && chown www-data:www-data /var/www/html/.phpunit.cache
|
||||
|
||||
# The entire FPM process runs as the same identity used for library checks.
|
||||
USER www-data
|
||||
ENTRYPOINT ["meshvault-entrypoint"]
|
||||
CMD ["php-fpm"]
|
||||
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
# Image and Compose both select www-data; validation and FPM inherit this identity.
|
||||
if [ "$1" = "php-fpm" ]; then
|
||||
if [ "$(id -u)" = 0 ]; then
|
||||
echo 'Refusing to validate or serve FPM as root; use www-data.' >&2
|
||||
exit 1
|
||||
fi
|
||||
php /var/www/html/bin/check-config.php
|
||||
fi
|
||||
exec docker-php-entrypoint "$@"
|
||||
@@ -0,0 +1,7 @@
|
||||
[www]
|
||||
; Preserve explicit Compose settings and forward Monolog JSON without prefixes.
|
||||
clear_env = no
|
||||
catch_workers_output = yes
|
||||
decorate_workers_output = no
|
||||
php_admin_flag[log_errors] = on
|
||||
php_admin_flag[display_errors] = off
|
||||
Reference in new issue
Block a user