Documentation / Linux Installation and Upgrade

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.sh and the clean v1 baseline at sql/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.tsv as pre-code or post-code before 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)
  • bash
  • curl
  • rsync for 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_URL
  • APP_TIMEZONE
  • FORCE_HTTPS
  • SECURE_COOKIES
  • MARIADB_DATABASE
  • MARIADB_USER
  • MARIADB_PASSWORD
  • MARIADB_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:

  1. validates required product files and .env;
  2. starts the database service and waits for readiness;
  3. refuses to continue unless the application database is empty;
  4. loads sql/baseline/v1.sql;
  5. verifies the baseline migration ledger;
  6. applies any future post-baseline migrations with explicit release phases;
  7. creates runtime media directories;
  8. repairs only the runtime-writable permissions that need it;
  9. builds/starts the Compose services;
  10. runs tools/recipe-site-health.sh;
  11. 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-code migrations run before release files change.
  • If any post-code migration 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.

  1. Read the upgrade log under .recipe-site/upgrade-logs/.
  2. Note the printed pre-upgrade DB dump and code snapshot paths.
  3. Determine whether the failure occurred before or after a migration/file-copy step.
  4. 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:

  1. add the SQL file under sql/migrations/;
  2. make the migration record itself in schema_migrations after successful work;
  3. add the filename and explicit pre-code or post-code phase to release/migration-phases.tsv;
  4. test it against a representative older release/database;
  5. 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.md
  • working/PROJECT_STATUS.md
  • working/NEW_THREAD_PROMPT.md