Provisioning now restores all GUI customizations on reset+reprovision: - create_pod_espocrm.sh: deploy the version-controlled espocrm-custom/ tree (CTag entity, layouts, i18n, clientDefs, custom views, custom CSS) into the pod, then chown www-data and rebuild. Replaces the earlier inline CSS-only step. Adds a live-phase cache rebuild so customizations and the client cacheTimestamp are refreshed on every run. - espocrm-custom/: snapshot of custom/ and client/custom/ (source of truth). - snapshot_espocrm_custom.sh: refresh the snapshot from a running pod. - readme.md: usage, first-time host setup, image-update and reset workflows. - Include the task/instruction notes and plan.md for reference. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
11 KiB
EspoCRM on DesTEngSsv006 — rootless Podman pod
This repository contains the shell scripts that run EspoCRM as a rootless
Podman pod under the local user mkt, plus the version-controlled snapshot of
all GUI customizations. Everything is driven by a handful of *.sh scripts; you
do not edit anything inside the running container by hand.
This is a private repository classified as a company secret. Plaintext passwords live directly in
create_pod_espocrm.shon purpose — keep the repo private.
1. What gets deployed
create_pod_espocrm.sh builds one pod with three containers:
| Container | Image | Role |
|---|---|---|
mariadb_ctr |
docker.io/library/mariadb:11.4.12 |
Database (utf8mb4 / utf8mb4_unicode_ci) |
espocrm_ctr |
docker.io/espocrm/espocrm:9.3.8 |
Web app (Apache) |
espocrm_daemon_ctr |
docker.io/espocrm/espocrm:9.3.8 |
Scheduler / cron loop |
Pod name: espocrm_pod. Images are pinned (no auto-updates).
Ports — loopback only (reach them via an SSH tunnel or a local reverse proxy):
| Host address | → | Container | Purpose |
|---|---|---|---|
127.0.0.1:8093 |
→ | :80 |
EspoCRM web UI |
127.0.0.1:8094 |
→ | :3306 |
MariaDB (host-side DB access) |
Web UI: http://127.0.0.1:8093 · Admin user: admin (password = the
ESPOCRM_ADMIN_PASSWORD value in create_pod_espocrm.sh).
Data lives in bind mounts on the host (not in named volumes):
~/.local/share/espocrm_pod/mariadb -> /var/lib/mysql (database files)
~/.local/share/espocrm_pod/espocrm -> /var/www/html (app, uploads, config, customizations)
Autostart: Podman 4.3.1 has no Quadlet, so the scripts generate
systemd --user units (podman generate systemd --files --new) and enable them.
The systemd-managed instance is what actually runs after provisioning.
2. The scripts at a glance
| Script | What it does |
|---|---|
create_pod_espocrm.sh |
Create/recreate the pod, deploy GUI customizations, rebuild, enable autostart. Safe to re-run. |
stop_pod_espocrm.sh |
Stop the running containers + pod (autostart stays enabled). |
backup_espocrm.sh |
Write a timestamped DB dump + a tarball of the app data dir to ~/bak. |
restore_espocrm.sh |
Restore a backup set (DB + files). --list shows sets, --yes skips the prompt. |
snapshot_espocrm_custom.sh |
Refresh the espocrm-custom/ snapshot from the running pod after GUI edits. |
reset_pod_espocrm.sh |
DANGER: delete the pod, units and all data. Requires typing a phrase. |
create_pod_traefik.sh |
Unrelated reference script (Traefik pod) — kept as the house-style template. |
All scripts are run as user mkt, from this directory:
cd ~/bin
./create_pod_espocrm.sh
3. First-time setup on a freshly installed host
The scripts assume the rootless-Podman groundwork is already in place. On a brand new host, verify these prerequisites first (one-time, usually needs root):
- Podman installed (4.3.x is what this was built for):
podman --version - User
mktexists and has subuid/subgid ranges (needed for rootless user-namespace mapping):grep '^mkt:' /etc/subuid /etc/subgid # each should print a line # if missing (as root): usermod --add-subuids 524288-589823 --add-subgids 524288-589823 mkt - Linger enabled for
mkt, so the user's systemd services start at boot and keep running without an active login:loginctl show-user mkt -p Linger # want: Linger=yes # if not: sudo loginctl enable-linger mkt - A user systemd session is reachable. When working over plain SSH you may
need:
export XDG_RUNTIME_DIR=/run/user/$(id -u) systemctl --user status # should respond
Then, as mkt:
cd ~/bin
./create_pod_espocrm.sh
On this first run the script will:
- pull the pinned images,
- run the EspoCRM installer against an empty database (creates the schema and the
adminuser from the credentials in the script), - deploy all GUI customizations from
espocrm-custom/, - run a cache rebuild,
- generate and enable the
systemd --userservices (autostart).
When it finishes, open http://127.0.0.1:8093 and log in as admin.
Reaching the UI from your workstation: it only listens on loopback. Use an SSH tunnel, e.g.
ssh -L 8093:127.0.0.1:8093 mkt@DesTEngSsv006, then browse tohttp://127.0.0.1:8093locally.
4. Everyday operations
# Start (after a stop) — the systemd units stay enabled, so this also happens on boot:
systemctl --user start pod-espocrm_pod.service \
container-mariadb_ctr.service container-espocrm_ctr.service container-espocrm_daemon_ctr.service
# Stop everything (autostart remains enabled):
./stop_pod_espocrm.sh
# Status:
systemctl --user status pod-espocrm_pod.service
systemctl --user status container-espocrm_ctr.service
# Live logs:
journalctl --user -u container-espocrm_ctr.service -f
Re-running ./create_pod_espocrm.sh at any time is safe: it recreates the
containers from the persisted data, redeploys customizations, and rebuilds.
5. Backups and restore
Backups are manual (no timer/cron). Each run writes two timestamped files to
~/bak: a gzipped SQL dump and a tarball of the app data dir (which includes
config.php with the crypt key, uploads, and customizations).
./backup_espocrm.sh # creates ~/bak/espocrm-db-<TS>.sql.gz and ...-files-<TS>.tar.gz
./restore_espocrm.sh --list # list available backup sets
./restore_espocrm.sh # restore the latest set (asks for confirmation)
./restore_espocrm.sh 20260606-1346 # restore a specific set by timestamp
restore_espocrm.sh checks the archives, drops & re-imports the database, and
replaces the app data dir, then rebuilds. Take a backup before any risky
operation (image update, reset, major change).
6. GUI customizations (important workflow)
All customizations made through the EspoCRM GUI (Entity Manager, Layout Manager,
Label Manager, custom fields/views, custom CSS, …) are stored in
espocrm-custom/ in this repo and deployed by create_pod_espocrm.sh. This
snapshot is the single source of truth — a reset + reprovision restores it.
Current customizations captured here include the CTag tag entity and its
relationships, the Tags panels (Create + button removed, row menu reduced to
Unlink), the signature-editor image button, and variable-width Markdown tables.
The golden rule: after you change anything in the EspoCRM GUI, capture it:
./snapshot_espocrm_custom.sh # pull current customizations into espocrm-custom/
git add espocrm-custom && git commit -m "Update EspoCRM customizations snapshot"
If you skip this, the next create_pod_espocrm.sh run will revert your
un-captured GUI changes back to whatever is in espocrm-custom/.
Note: customizations are files. Your actual records (tags, opportunities, contacts, …) are data and live only in the database — protect those with
backup_espocrm.sh, not the snapshot.
7. Updating the container images
Images are pinned, so updates are deliberate. Always back up first.
./backup_espocrm.sh
EspoCRM (e.g. 9.3.8 → a newer 9.x)
- Edit the version in
create_pod_espocrm.sh(the web and daemon share the sameESPO_IMAGEvariable, so one change covers both):ESPO_IMAGE='docker.io/espocrm/espocrm:<new-version>' - Pull and reprovision:
The EspoCRM container entrypoint auto-runs its upgrade when the image version is newer than the installed version (it migrates the database and app files on the bind mount). The script then redeploys customizations and rebuilds.
podman pull docker.io/espocrm/espocrm:<new-version> ./create_pod_espocrm.sh - Verify the UI and your customizations. If you tweaked anything in the GUI as
part of the upgrade, re-run
./snapshot_espocrm_custom.shand commit.
MariaDB (e.g. 11.4.12 → a newer 11.4.x patch)
Patch/minor updates within the same 11.4 LTS series are data-compatible:
# edit DB_IMAGE in create_pod_espocrm.sh, then:
podman pull docker.io/library/mariadb:<new-11.4.x>
./create_pod_espocrm.sh
For a major MariaDB jump (e.g. 11.x → 12.x) do not just swap the tag — back up, then dump/restore into a fresh data dir, and test. When in doubt, take a backup and try it on a throwaway copy first.
After any image change
podman ps # all three containers Up
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8093/ # expect 200
podman image prune # optionally drop the old images
Commit the bumped image tag(s) in create_pod_espocrm.sh.
8. Resetting / uninstalling
reset_pod_espocrm.sh destroys the pod, the systemd units, and ALL data
(database + uploads). It asks you to type RESET espocrm_pod to proceed.
./backup_espocrm.sh # do this first if you might want the data back
./reset_pod_espocrm.sh
./create_pod_espocrm.sh # fresh install; customizations are restored from espocrm-custom/
# (optional) restore your data:
./restore_espocrm.sh
After a reset, a fresh create_pod_espocrm.sh gives you a clean install with all
customizations back in place; records come back only via restore.
9. Repository layout
create_pod_espocrm.sh # provision + autostart + deploy customizations
stop_pod_espocrm.sh # stop the pod
backup_espocrm.sh # back up DB + files to ~/bak
restore_espocrm.sh # restore a backup set
snapshot_espocrm_custom.sh # refresh espocrm-custom/ from the running pod
reset_pod_espocrm.sh # DANGER: delete pod + units + all data
espocrm-custom/ # version-controlled GUI customizations (source of truth)
create_pod_traefik.sh # unrelated reference script (house-style template)
*.md # task/instruction notes and this readme