Containers: Docker and Podman
This page is for hosting companies and platform teams that run Exponential sites in Linux containers with Docker or Podman. It answers what the project's sources say about containers, what must live on a persistent volume, how many containers may write a SQLite database, how an entry point installs a site once and keeps the server running, and how to keep secrets out of images.
Warning
.htaccess slows down every request. When .htaccess files are enabled (AllowOverride anything but None), let alone
used, Apache looks for them and reads and evaluates them on every request, in every directory of the path, and
page loading times go up drastically (measured). The only real fix is to switch the feature off for
the virtual host: AllowOverride None in the HTTPS virtual host, and the rules in the virtual host's own
configuration as mod_rewrite rules. That is far faster in production speed tests and well worth the time it takes
to optimize a production configuration. Switch over right away: copy the rules of the
example HTTPS virtual host into your virtual host (with the static cache, also
staticcache_offload.conf), set AllowOverride None, run apachectl configtest, reload Apache and
test the site. The same rules for other servers: nginx, LiteSpeed,
Caddy, IIS, Velocity (built in); where virtual host rules go in a control
panel: Plesk, cPanel, DirectAdmin, ISPConfig.
On LiteSpeed or OpenLiteSpeed, the server's own page cache can also answer anonymous visitors and crawlers without PHP: Page caching with LiteSpeed and OpenLiteSpeed.
Contents
- What the sources say
- Images
- Volumes: what must persist
- The database and the one-writer rule
- An entry point that installs once
- Secrets
- Operations inside containers
- Checklist
What the sources say
- Docker and Podman (Linux containers) are tier 2, known to work: "Velocity publishes images on
ghcr.io/se7enxweb/exponential-velocityfor amd64 and arm64, one per PHP version and variant" (Tier 2). - "Any image with PHP 8 and the extensions above runs Exponential ... Keep
var/(storage, logs, the SQLite database) andsettings/overrideon a persistent volume. With SQLite, run one container that writes" (Containers, cloud and hosting panels). exp:installis the method for "CI pipeline, container image, automated test environment": options or environment variables, exit status (4.6 Unattended installs).
Images
Two routes:
| Route | What you get | What to watch |
|---|---|---|
| Your own image from a PHP 8.0+ base | the PHP extensions you choose, Composer, the installation built with composer install | install every required extension: dom, xsl, intl, mbstring, iconv, zlib, one database extension, gd or ImageMagick, and for Velocity sockets, pcntl, openssl |
| Velocity's published image as a base | the server and a PHP with a defined extension set | check the variant against what Exponential needs |
Velocity's images are tagged by release, PHP version and variant (Velocity packages, Docker images and binaries):
ghcr.io/se7enxweb/exponential-velocity:<release>-php<version>-<variant> fixed
ghcr.io/se7enxweb/exponential-velocity:php<version>-<variant> moves with each release
ghcr.io/se7enxweb/exponential-velocity:latest newest PHP, standard variant<version> is 8.2, 8.3, 8.4 or 8.5; <variant> is mini, lite, standard or full. "Pin a fixed tag in
production." The standard variant is "the default; what the documentation assumes".
Tip
Whatever the base, run php bin/php/console exp:velocity ext check inside the built image as part of your image
pipeline. It lists every extension the installation wants, marks what is missing and prints the install command
(Finding what is missing).
Volumes: what must persist
| Path | Holds | Volume |
|---|---|---|
var/ | uploads and image variations (var/storage), caches, logs, autoload arrays, the SQLite database (var/storage/sqlite3/) | persistent |
settings/override | the installation's settings, including the database connection | persistent |
settings/siteaccess | the siteaccess settings the installer writes | persistent (the backup list names it) |
kernel/, lib/, vendor/, extension/, design/ | the code | in the image |
The backup set is the same as everywhere: the database and var/storage, settings/override and
settings/siteaccess (Backup and restore).
Warning
Files on the volume must belong to the user the container's PHP runs as. "Files created by root may later be
unwritable for the web server" (4.6).
Decide the user ID once for the image and the volume, and keep it.
The database and the one-writer rule
In the official order: 1. SQLite 3 (the default) · 2. PostgreSQL · 3. MongoDB · 4. MariaDB · 5. Oracle · 6. MySQL. Microsoft SQL Server is known to be possible with additional work at this time.
- 1. SQLite 3 works in a container when one container writes the database: "SQLite's WAL needs shared memory on one host, so several replicas cannot share one database file over a network volume."
- Several containers serving one site need a database server ("PostgreSQL first") and shared storage for
var/storage(Containers, cloud and hosting panels). They also need sessions every container can read; see Kubernetes. - The database server usually runs in its own container or as a managed service of your platform, with its own volume and its own backups (Database backups per engine).
An entry point that installs once
Chapter 4 gives "a minimal container entry point" that installs once, then starts Velocity and stays in the foreground while it runs:
#!/usr/bin/env bash
set -euo pipefail
cd /srv/exponential
if ! grep -q '^\[DatabaseSettings\]' settings/override/site.ini.append.php 2>/dev/null; then
php bin/php/console exp:install --apply \
--db=mysql --db-host="${DB_HOST}" --db-name="${DB_NAME}" --db-user="${DB_USER}" \
--url="${SITE_URL}" --email="${ADMIN_EMAIL}" --password="${ADMIN_PASSWORD}"
fi
php bin/php/console exp:velocity start
# exp:velocity start runs the server in the background and returns; status exits 0 while it runs
while php bin/php/console exp:velocity status >/dev/null 2>&1; do sleep 30; done
echo "Velocity stopped" >&2
exit 1What the chapter explains about it:
- The
grep"is the same testexp:installitself uses to decide that a directory already holds an installation". - "
exp:velocity startdetaches the server and returns as soon as it answers", so the loop keeps the container's main process alive and ends it when the server stops, "so the container runtime can restart it". - "If the entry point runs as
root, add--allow-root-userto the twoexp:velocitycommands." - The example uses
--db=mysql; the other values aresqlite(the default),pgsql,mongodb,mariadbandoracle(Database support, At a glance).
Warning
exp:install uses the database action remove by default. That "is what a fresh container wants, and exactly what
must never point at a database that holds data you need" (4.6).
Keep the "already installed" test in front of it.
Secrets
- Pass the database password in
EXP_INSTALL_DB_PASSWORD;exp:installreads it "when--db-passwordis not given; that keeps it out of the process list and the shell history". - Without
--password,exp:installgenerates a 24-character administrator password and writes it tovar/log/initial-admin-password(mode0600); read it from there and rotate it, or pass one from your secret store (First sign-in and default passwords). - Never bake
settings/overrideinto an image: it holds the database credentials (13.3 Secrets in settings/override). config.phpcan be made in the image (exp:setup:config --apply --allow-root-user --owner=www-data:www-datain the build step), but a password in it, such asCLUSTER_STORAGE_PASS, is set at start from the environment:php bin/php/console exp:config:set CLUSTER_STORAGE_PASS --value="$CLUSTER_DB_PASS" --apply --allow-root-user. The output shows it as********. In CI,exp:config:list --only-set --format=jsonshows what an image or a running container sets (Setting up .htaccess and config.php from the console).
Operations inside containers
- Cron.
runcronjobs.phpstill has to run on a schedule: from a scheduler container that shares the volumes and the database, or from your platform's scheduler, as the same user as the web process (Cron jobs). - Logs. Exponential writes its logs under
var/log(and per site undervar/<site>/log); keep them on the volume or collect them from there (Request logs). - Code changes reach Velocity's workers only after a restart;
exp:velocity deployruns the steps in order (8.3.14). In containers the usual equivalent is a new image and a new container.
Checklist
- To do: Image with PHP 8.0+ and every required extension;
exp:velocity ext checkpasses in the pipeline. - To do: Fixed image tags in production.
- To do:
var/,settings/override,settings/siteaccesson persistent volumes, owned by the container's PHP user. - To do: One writing container for SQLite, or a database server for more.
- To do: Entry point with the "already installed" test; secrets from the environment, not the image.
- To do: Cron, logs and backups defined.
A public demo in a container. Give the demo service EXP_DEMO_MODE=1 (-e, --env-file or env_file:; docker-demo.env), never a base image other sites use. With PHP-FPM inside, keep clear_env = no or set env[EXP_DEMO_MODE] = 1 in the pool. Demo mode for hosts.
Related pages
- Kubernetes, Platform as a service
- exp:install, Automating provisioning
- Installing in one command
- Setting up .htaccess and config.php from the console
- Free documentation: this page is free to copy, translate and improve under the GNU FDL 1.3.
Become an Exponential Partner
Partners share their information, provide funding, contribute back and develop the products with us, and that is how Exponential grows. A partner website and dashboard are planned for mid-2027: please become a partner today to speed up our growth rate and land new features faster.