Installation and Upgrade Guide
Updated: 2026-09-04
This guide covers the supported Recipe Site product installer and release-to-release upgrader on a Linux host using Docker Engine and Docker Compose v2.
Supported model
The product now treats application files and database migrations as one controlled release operation.
- Fresh installs use
tools/recipe-site-install.shand the clean v1 baseline atsql/baseline/v1.sql. - Existing installations use the new release's
tools/recipe-site-upgrade.sh. - The 41 pre-v1 migrations through account email verification are represented by the v1 baseline and are recorded in
schema_migrations; they are not replayed against an empty database. - Any migration added after the v1 baseline must be explicitly classified in
release/migration-phases.tsvaspre-codeorpost-codebefore the upgrader will run it. .env, runtime media, local release state, and backups are preserved across upgrades.- Before a real upgrade changes schema or application files, the upgrader creates and verifies a database dump and a code snapshot.
- Every successful install/upgrade ends with automated health checks.
The installer/upgrader does not assume a particular home directory or server layout. Portable backup, restore, and disaster-recovery procedures are documented separately in Backup and Restore.
1. Requirements
- Supported Linux server/VM
- Docker Engine
- Docker Compose v2 (
docker compose) bashcurlrsyncfor upgrades- Git when installing from a checkout; a release archive can also be used
- An HTTPS reverse proxy/tunnel for access beyond localhost
Check the main prerequisites:
docker --version
docker compose version
bash --version
curl --version
rsync --version
MariaDB and PHP run inside the product containers; they do not need to be installed on the host.
2. Obtain the release
For a Git checkout:
git clone <repository-url> recipe-site
cd recipe-site
For a packaged release, extract it into its own installation directory and enter that directory.
Do not copy a production .env, database volume, or runtime image tree into a fresh installation unless you are intentionally performing a restore rather than a clean install.
3. Configure .env
The repository includes a safe template:
.env.example
The installer deliberately does not invent database passwords or deployment values. If .env is absent, the first real installer run copies .env.example to .env, sets restrictive permissions, and stops so the operator can edit it.
You may also create it yourself:
cp .env.example .env
chmod 600 .env
nano .env
At minimum review:
APP_BASE_URLAPP_TIMEZONEFORCE_HTTPSSECURE_COOKIESMARIADB_DATABASEMARIADB_USERMARIADB_PASSWORDMARIADB_ROOT_PASSWORD- matching
DB_*values BACKUP_DIR- mail settings if outbound account email will be used (
MAIL_HOST,MAIL_PORT,MAIL_USERNAME,MAIL_PASSWORD,MAIL_ENCRYPTION,MAIL_FROM_ADDRESS,MAIL_FROM_NAME)
Optional per-install values allow more than one Recipe Site on a Docker host:
WEB_CONTAINER_NAME
DB_CONTAINER_NAME
PHPMYADMIN_CONTAINER_NAME
WEB_PORT
PHPMYADMIN_PORT
Defaults preserve the existing deployment convention:
recipe_web 127.0.0.1:8080->80
recipe_phpmyadmin 127.0.0.1:8081->80
recipe_db internal only
Never commit the real .env.
Account-request email
The product supports administrator approval notifications and optional requester email verification. SMTP credentials remain installation secrets in .env; the approval address and verification policy are owner-editable in Admin -> Site Settings.
The relevant .env values are:
ACCOUNT_APPROVAL_EMAIL
REQUIRE_EMAIL_VERIFICATION
MAIL_ENABLED
MAIL_HOST
MAIL_PORT
MAIL_USERNAME
MAIL_PASSWORD
MAIL_ENCRYPTION
MAIL_FROM_ADDRESS
MAIL_FROM_NAME
Keep MAIL_ENABLED=0 and REQUIRE_EMAIL_VERIFICATION=0 until outbound SMTP has been configured and tested. Never place the real mail password in source control or database-backed Site Settings.
4. Fresh-install preflight
A dry run changes nothing:
bash tools/recipe-site-install.sh --dry-run
If .env does not exist, the dry run reports that configuration is required. After .env is configured, run the dry run again.
The fresh installer is fail-closed: after the MariaDB service starts, the selected application database must contain zero tables. If it already contains tables, use the upgrader for an existing Recipe Site or the documented restore process for a recovery.
5. Fresh install
Run:
bash tools/recipe-site-install.sh
For noninteractive automation after you have independently verified the target and .env:
bash tools/recipe-site-install.sh --yes
The installer performs these operations in order:
- validates required product files and
.env; - starts the database service and waits for readiness;
- refuses to continue unless the application database is empty;
- loads
sql/baseline/v1.sql; - verifies the baseline migration ledger;
- applies any future post-baseline migrations with explicit release phases;
- creates runtime media directories;
- repairs only the runtime-writable permissions that need it;
- builds/starts the Compose services;
- runs
tools/recipe-site-health.sh; - records local release state under
.recipe-site/.
Successful completion ends with:
INSTALL=PASS
What the v1 baseline contains
The baseline is a clean product database, not a production clone. It contains:
- the current 33-table schema;
- current foreign keys/indexes/column definitions;
- built-in roles;
- permissions and role-permission grants;
- the current category taxonomy;
- generic owner-editable site-setting defaults;
- migration-history rows for the 41 migrations already represented by the baseline.
It intentionally contains no users, recipes, ratings, favorites, cooking history, shopping data, audit records, import logs, production contact endpoint, or production ID counters. tags and recipe_tags remain in the schema for possible future tag support, but the baseline seeds no tags.
6. Create the first Owner
After INSTALL=PASS, open:
/setup-owner.php
Locally, using the default port:
http://127.0.0.1:8080/setup-owner.php
The first-owner page:
- works only while no active Owner exists;
- requires the established 12-character minimum for a user-selected password;
- creates the initial Owner and logs that user in;
- locks itself after an active Owner exists;
- fails closed if it cannot safely determine Owner state.
See First Owner Setup.
7. Health checks
The supported health check is:
bash tools/recipe-site-health.sh
It verifies, among other things:
- database and web Compose services are running;
- database connectivity;
- critical PHP syntax;
- runtime media directories;
- no pending/unclassified release migrations;
mariadb-check --check --quick;- local home/login/setup endpoints;
- MariaDB has no host port binding.
Success ends with:
RECIPE_SITE_HEALTH=PASS
8. Release-to-release upgrade
Do not overwrite the current installation with new release files first. Obtain/extract the new release into a separate directory, then run the upgrader from that new release against the existing installation.
Example layout:
/home/user/recipe-site # existing installation
/home/user/recipe-site-release-new # newly downloaded/extracted release
From the new release tree:
cd /home/user/recipe-site-release-new
bash tools/recipe-site-upgrade.sh \
--install-dir /home/user/recipe-site \
--dry-run
Review the reported release/migration/copy plan. Then run the real upgrade:
bash tools/recipe-site-upgrade.sh \
--install-dir /home/user/recipe-site
For separately controlled automation:
bash tools/recipe-site-upgrade.sh \
--install-dir /home/user/recipe-site \
--yes
A successful upgrade ends with:
UPGRADE=PASS
9. What the upgrader preserves
The release copy deliberately excludes these installation-specific paths:
.env
.git/
.recipe-site/
www/assets/images/
backups/
node_modules/
That preserves local secrets/configuration, Git metadata when present, release logs/state, recipe/avatar/import/site images, in-tree backups if someone uses them, and local dependency state.
The product's default backup location is outside the installation tree (../recipe-site-backups) so release-file replacement cannot remove backups.
10. Upgrade safety and ordering
Before changing schema or release files, the upgrader creates:
- a fresh MariaDB dump named like
pre-upgrade-recipes_*.sql; - a SHA-256 checksum for that dump;
- a pre-upgrade code snapshot named like
pre-upgrade-code_*.tar.gz; - a SHA-256 checksum for that snapshot;
- an upgrade log under
.recipe-site/upgrade-logs/.
The migration runner compares sql/migrations/*.sql with the database schema_migrations ledger.
Every pending migration must appear in:
release/migration-phases.tsv
with one of:
pre-code
post-code
The upgrader refuses an unclassified pending migration rather than guessing the required order.
pre-codemigrations run before release files change.- If any
post-codemigration is pending, the web service is stopped before new application files are installed, those migrations are then applied, and the service is rebuilt/restarted afterward.
Each applied migration must register its own filename in schema_migrations; otherwise the upgrade stops.
This protects against running new application code against an incompatible schema and against silently replaying historical migrations.
11. Failure / recovery
If an upgrade stops before UPGRADE=PASS, do not keep retrying blindly.
- Read the upgrade log under
.recipe-site/upgrade-logs/. - Note the printed pre-upgrade DB dump and code snapshot paths.
- Determine whether the failure occurred before or after a migration/file-copy step.
- Use those verified recovery artifacts and the backup/restore procedures before making further destructive changes.
The upgrader intentionally creates recovery material before changing the installation, but full clean-machine disaster-recovery testing is a separate v1 release-readiness step. See Backup and Restore.
12. HTTPS / public access
The Compose web service and phpMyAdmin bind to 127.0.0.1 by default. MariaDB has no host port binding.
Expose the site through HTTPS using an appropriate reverse proxy/tunnel such as Cloudflare Tunnel, Caddy, Nginx, or Apache proxy. Do not expose MariaDB or phpMyAdmin directly to the internet.
For a network-accessible production installation keep:
FORCE_HTTPS=1
SECURE_COOKIES=1
unless the deployment architecture has a documented reason otherwise.
13. Runtime permissions
The portable permission helper is:
bash fix-permissions.sh
It confines writable ownership/mode repair to runtime media directories instead of recursively rewriting source-code ownership. It determines the installation owner dynamically and uses www-data when available.
Do not use world-writable 777 as a normal configuration.
14. Backup / restore and host maintenance
An existing installation may also have its own host-specific scheduled backup and retention system. Those local scripts are separate from the portable product installer and recovery tools.
For backup and recovery procedures see:
The self-hosted development runner cleanup remains separate:
bash tools/cleanup-runner-retention.sh --dry-run
Do not use runner cleanup to delete database/media/site backups.
15. Release maintenance rule
When adding a database migration after the v1 baseline:
- add the SQL file under
sql/migrations/; - make the migration record itself in
schema_migrationsafter successful work; - add the filename and explicit
pre-codeorpost-codephase torelease/migration-phases.tsv; - test it against a representative older release/database;
- verify backup, preservation, ordering, and health-check behavior before shipping the release.
Do not edit sql/baseline/v1.sql for ordinary post-v1 upgrades. It is the v1 fresh-install starting point; evolution from shipped releases belongs in tracked migrations.
16. Documentation
Start with Documentation Index.
For development continuity also read:
working/CHAT_HANDOFF.mdworking/PROJECT_STATUS.mdworking/NEW_THREAD_PROMPT.md
