Standalone local development environments for Evolution CMS, powered by Docker.
cd /path/to/your/evolution-project
silo doctor
silo up -dSilo runs Evolution CMS. It is a separate Go executable: your application does
not need a Silo Composer dependency, Dockerfile, Compose file or generated web
server configuration. Silo manages its infrastructure outside your application,
under ~/.silo.
Status: initial development version (0.1.0-dev). Build from source for now;
there is no published binary release, installer or self-update command yet.
The current runtime is PHP 8.4 + Apache + MariaDB 11.4. This starts an
environment for an existing project and can restore its latest local SQL backup
on first database initialization. It does not install the CMS. Full compatibility with an individual application must be checked
separately.
- Requirements
- Build Silo
- First startup
- Commands
- HTTP and HTTPS
- Runtime and database
- Configuration and state
- Troubleshooting
- Development and tests
- Current scope
| Component | Requirement |
|---|---|
| Windows | amd64, Docker Desktop using Linux containers |
| WSL | WSL2 with Docker integration enabled for the distribution |
| Linux | amd64 or arm64, local Docker Engine |
| macOS | amd64 or arm64, local Linux container engine such as Docker Desktop |
| Docker Compose | Compose plugin 2.20 or newer, available as docker compose |
| Go | 1.23 or newer, only to build Silo |
| Application | Evolution CMS project with the detection markers below |
Docker must be installed, running and accessible to your current user. Check:
docker --version
docker info
docker compose versionUse a local Docker context. Remote daemons are outside this first version's scope because project bind mounts refer to the daemon's filesystem.
The first startup downloads base images and builds the PHP runtime; internet
access and free Docker disk space are required. Images come from
public.ecr.aws/docker/library/php:8.4-apache and
public.ecr.aws/docker/library/mariadb:11.4; the build also uses Debian package
mirrors. These image references do not use Docker Hub. Public Silo images on
GHCR are planned, but are not required or assumed to exist in this version.
Silo does not require PHP or Composer on the host to run. Your application's PHP dependencies must already be installed before the CMS itself can work.
Clone the source and build a native executable. No global installation or administrator access is needed for the Silo binary.
git clone https://github.com/evolution-cms/silo.git
cd silo
go build -o .\bin\silo.exe .\cmd\silo
.\bin\silo.exe versionAdd the bin directory to your user PATH, or use the executable's full path:
cd H:\Projects\my-evolution-site
& 'H:\Tools\silo\bin\silo.exe' doctor
& 'H:\Tools\silo\bin\silo.exe' up -dReplace the sample executable and application paths with your actual paths.
git clone https://github.com/evolution-cms/silo.git
cd silo
go build -o ./bin/silo ./cmd/silo
./bin/silo versionAdd that bin directory to PATH, or invoke its absolute path from your project:
cd /path/to/my-evolution-site
/path/to/silo/bin/silo doctor
/path/to/silo/bin/silo up -dIn WSL, build/use the Linux binary and Linux paths, for example
/mnt/h/Projects/my-evolution-site. Native Windows and WSL use different home
directories and project identities; use one consistently for a given local
environment. Running both against the same application can cause port conflicts
and produces separate database volumes.
- Start Docker and select Linux containers.
- Open a terminal in your existing Evolution application.
For an existing site, place its SQL backup in
assets/backupbefore first startup. - Run
silo doctorand resolve any[FAIL]checks. - Run
silo up -d. - Open http://127.0.0.1:8080.
- Use
silo psto inspect services andsilo downto stop them.
If port 8080 is already used, choose a different port on first startup or change the port of an existing environment with the same command:
silo up -d --port 8087Then open http://127.0.0.1:8087. Silo remembers the port; subsequent starts use
silo up -d. Compose recreates the web container when its port changes; database
content, credentials and project identity remain intact. No preliminary down
is required. A brief HTTP interruption is expected while the web container is
recreated. If startup fails, the selected port remains saved; retry with another
--port if necessary. Automatic port selection is not implemented.
Give each project a different HTTP port. For example, in PowerShell with silo
on PATH:
cd H:\Projects\site-one
silo up -d --port 18080
cd H:\Projects\site-two
silo up -d --port 18081Each project has its own containers, network, database volume, credentials and
saved port, even when directory basenames match. The PHP runtime image/build
cache can be shared. silo down affects only the project selected by the current
directory. HTTP ports must be unique; database ports remain internal and do not
conflict. Silo does not stop another project to free an occupied port.
The first build takes longer than later startups. Detached startup waits for Compose readiness; a ready container alone does not prove that the CMS has its dependencies, tables, content or correct application-specific configuration.
Silo walks upwards from the current directory and selects the closest directory containing all of:
index.php
core/bootstrap.php
core/composer.json
The Composer file must identify evolution-cms/evolution or
evolutioncms/evolution in its name field. Commands also work from subdirectories
such as core/custom. An unrelated PHP application is not accepted merely
because it has an index.php.
Older layouts without these markers are not supported by this first slice.
| Command | Behavior |
|---|---|
silo --help |
Show supported commands and options |
silo version |
Print CLI version and OS/architecture; Docker is not needed |
silo doctor |
Diagnose platform, Docker, Compose, Linux daemon, WSL where relevant, project and state location |
silo up |
Create/reuse external state and start with attached Compose logs |
silo up -d |
Start in the background and wait for Compose readiness |
silo up --detach |
Same as silo up -d |
silo up -d --port 8087 |
Set or change this project's saved HTTP port |
silo up -d --https-port 18443 |
Enable HTTPS alongside HTTP, or change its saved port |
silo ps |
Show this project's containers |
silo down |
Remove this project's containers/network; keep database volume and Silo state |
silo ps and silo down report that no environment exists if the project has
never been started; they do not create one. Commands fail with nonzero exit
status on error. Docker command failures retain Docker's exit code.
For attached silo up, keep the terminal open for logs. Use silo down in a
second terminal to stop/remove the environment explicitly. Console interruption
behavior is delegated to Docker Compose.
There is deliberately no generic Compose passthrough in this version.
Unsupported commands and flags, including silo down -v, are rejected.
HTTP remains supported on its saved port. To enable HTTPS as well:
silo up -d --port 18080 --https-port 18443Both endpoints are then available:
http://127.0.0.1:18080/
https://127.0.0.1:18443/
localhost can also be used. Both ports bind to IPv4 loopback only and must be
different and available. Assign different HTTP/HTTPS port pairs to concurrent
projects. The HTTPS port is remembered: later silo up -d keeps both protocols.
--https-port N changes the HTTPS port without changing HTTP; --port N changes
HTTP without disabling HTTPS. HTTPS is opt-in and has no automatic disable flag
in this version; passing zero is rejected.
Silo itself does not force HTTP to HTTPS. It serves the application on both
protocols and PHP sees the actual TLS state. If the application redirects from
HTTP to HTTPS while retaining the incoming HTTP port, Apache maps that local
Location header to the saved HTTPS port. The inverse mapping also works.
Only absolute 127.0.0.1/localhost redirects with the corresponding source
port are adjusted; external hostnames, already-correct URLs and response bodies
are not rewritten. Production domain redirects or hardcoded links require
application configuration. An HTTP-only site continues to work over HTTP.
Silo generates a separate local CA for each project and a certificate for
localhost, 127.0.0.1 and ::1. TLS uses Apache directly, without a reverse
proxy. Existing environments are upgraded in external state; DB content,
credentials and the backup initialization marker are retained.
Until you trust that project's CA, a browser will report an untrusted issuer. Silo does not install trust automatically. After the first HTTPS startup, use the CA path printed by Silo. In PowerShell:
# Replace PROJECT_ID (and the state root if SILO_HOME is customized).
$ca = "$env:USERPROFILE\.silo\projects\PROJECT_ID\tls\ca.crt"
Import-Certificate -FilePath $ca -CertStoreLocation Cert:\CurrentUser\RootThis adds the CA to the current Windows user's trusted roots. Import only the
public ca.crt; keep ca.key private. Restart the browser if needed. Some
browsers use a separate certificate store and require their own CA import.
For WSL-hosted state, import its public CA into the Windows store if the browser
runs on Windows. On Linux/macOS, import it into the OS/browser trust store using
the normal certificate-management tools for that system.
The CA stays stable when ports change. Leaf certificates are renewed on up
when fewer than 30 days remain; the web container is recreated when its
certificate or TLS configuration changes. An incomplete/expired CA is reported
instead of silently replacing a previously trusted authority. To revoke trust,
remove that specific Silo local CA <project-id> from your certificate store.
For command-line verification without changing a trust store:
curl.exe --cacert $ca https://127.0.0.1:18443/https://127.0.0.1:18080/ is incorrect when 18080 is the HTTP port and results in
ERR_SSL_PROTOCOL_ERROR. Use the printed HTTPS URL. A browser-cached redirect
from an earlier HTTP-only session may need clearing.
- PHP 8.4 and Apache with
mod_rewrite. - PHP extensions: GD, mysqli, PDO MySQL, ZIP, mbstring and intl, in addition to the base PHP image's extensions.
- GD includes JPEG, PNG, FreeType and WebP support (
imagewebp). Runningsilo upafter updating Silo refreshes the generated Dockerfile and rebuilds changed runtime layers for existing HTTP and HTTPS environments; database data is retained. - Application bind-mounted at
/var/www/html. - HTTP and optional HTTPS exposed on
127.0.0.1only. - Upload/post limit: 100 MB; memory limit: 256 MB; timezone: UTC.
- Runtime build context extracted from the CLI into external state.
Silo itself creates no files in the application. The application mount is writable: normal CMS cache, log and upload operations may write to the project. It is not a read-only preview or a filesystem sandbox.
MariaDB 11.4 runs as db inside an isolated Compose network. Port 3306 is not
published to the host. Its data lives in a project-specific named Docker volume.
The web service waits for the database healthcheck.
The web container receives:
| Variable | Value |
|---|---|
APP_ENV |
local |
DB_CONNECTION |
mysql |
DB_HOST |
db |
DB_PORT |
3306 |
DB_DATABASE |
evo, or the literal name from the example file below |
DB_USERNAME |
silo |
DB_PASSWORD |
Generated once and retained in external state |
Silo generates separate user and root passwords. The web service does not receive the database root password.
silo down does not delete database data. Restarting with silo up -d reuses
the same database and credentials. Preserve the external environment file
alongside the database volume; manually deleting state can leave an existing
volume whose credentials no longer match newly-generated ones.
Before starting the web service, Silo starts MariaDB and waits for it to become
ready. On the first bootstrap of an empty database, it checks assets/backup:
- Supported files: regular, nonempty
.sqland.sql.gzfiles directly in that directory. Symlink files, ZIP archives and other file types are ignored. - The newest file is selected by last modification time; equal timestamps are resolved by filename, choosing the lexicographically last name. Preserve backup timestamps when copying, or leave only the intended backup there.
- SQL streams directly to the local MariaDB service. Gzip decompression is also streamed; the dump is not copied into Silo state or unpacked into the project.
- The project DB account is used. Dumps should target the selected database
without
CREATE DATABASE/USEdirectives naming another database, and should not require server-administrator privileges. Silo does not rewrite SQL. - Existing tables are never overwritten automatically. Already-initialized
databases are skipped, including after changing a port or running
down. - If there is no supported backup, Silo records initialization and starts with an empty DB. Adding a backup later does not trigger an automatic import.
Bootstrap markers live in the database volume, not just in Silo metadata. Existing volumes created by an earlier Silo version can be bootstrapped if they have no marker and contain no tables. A populated legacy volume is marked as initialized without importing anything.
If an import fails or is interrupted, Silo retains partial data and blocks automatic retry/web startup. It does not delete tables or retry another backup. Inspect and recover that isolated database deliberately; do not remove its pending marker to retry against partial data. SQL-client output is suppressed during import because failed statements can contain private site data.
Successful SQL import does not rewrite site URLs, run migrations, run the Evolution installer, execute Composer scripts or fix application-specific PHP configuration. Those remain separate integration steps.
Evolution configurations that use immutable dotenv/environment values can use
these container settings without rewriting .env. Hardcoded PHP DB connection
values or application-specific overrides require separate integration work;
Silo cannot promise to override them. Review an application's configuration
before using it as a real integration target.
If core/custom/.env.docker.example exists, Silo reads only a literal
DB_DATABASE value from it:
DB_DATABASE=my_local_siteSimple single/double quotes are allowed. Names may contain letters, numbers, underscores and hyphens, up to 64 characters, and must begin with a letter, number or underscore. Shell commands, variable expansion and inline comments on this setting are not supported.
Other values are ignored. Silo never copies credentials from the example or from
the application's real .env, and never sources an env file as a shell script.
The selected database name is fixed when state is first created.
~/.silo/
└── projects/
└── <project-id>/
├── metadata.json
├── compose.yaml
├── environment
└── runtime/
├── Dockerfile
└── php.ini
On native Windows, ~ is your user profile, normally C:\Users\<you>.
compose.yaml is generated as JSON, which is valid YAML accepted by Compose.
The environment file contains development credentials; do not commit or share
it. POSIX permissions are restricted to the owner. On Windows, protect the
directory with your user-profile ACLs.
To use a different absolute directory outside the application:
# PowerShell, current terminal session
$env:SILO_HOME = 'D:\SiloState'
silo up -d# Linux/macOS/WSL, current terminal session
export SILO_HOME="$HOME/.local/share/silo"
silo up -dUse the same SILO_HOME for later ps, down and up commands. Changing it does
not move existing state or volumes. Silo rejects a state path inside the
application, including paths that resolve there through symlinks.
Project identity is derived from its canonical absolute path. Projects with the same basename remain separate; symlink aliases share an identity. Moving a project creates a new identity and leaves its previous state/data intact. There is no state migration or reset command yet.
Silo passes its Compose file, env file, project name and project directory
explicitly. Ambient COMPOSE_*, DB_*, MARIADB_* and APP_ENV variables are
excluded from Compose subprocesses so unrelated shell settings cannot redirect
the project or replace its generated database credentials. Standard Docker
context and authentication settings are preserved.
State creation is staged and protected by a project lock. Existing damaged or
incompatible state causes an error instead of silently replacing credentials.
With HTTPS enabled, tls/ also contains ca.crt, ca.key, server.crt,
server.key and apache.conf. Only the leaf certificate/key and Apache config
are mounted read-only into the web container; the CA private key stays on the
host. These files are never written to the application.
| Symptom | Check/action |
|---|---|
silo is not recognized |
Use the executable's full path or add its directory to PATH |
| Docker CLI unavailable | Install Docker and confirm docker --version works in the same terminal |
| Docker daemon unavailable | Start Docker Desktop/the Docker service; check docker info |
| Linux containers required | Switch Docker Desktop from Windows containers to Linux containers |
| Compose unavailable | Install/enable the Compose plugin; confirm docker compose version |
| WSL2 not detected | Use WSL2 and enable Docker Desktop integration for that distribution |
| No Evolution project found | Change to the application directory and verify the detection markers |
Missing core/vendor/autoload.php |
Install the application's PHP dependencies separately; Silo does not do this automatically |
| Port already allocated | Retry silo up -d --port 18080 with a free port; other projects are not stopped |
| Need another HTTP port | Run silo up -d --port N; the new port is saved for future starts |
| HTTPS protocol error | Use the saved HTTPS port, not the HTTP port; enable it with --https-port N |
| Certificate issuer not trusted | Import this project's public tls/ca.crt into the browser/OS trust store |
| Backup import failed/incomplete | Web startup is blocked; inspect the local partial database and backup before recovery |
| Image pull/build fails | Check access to ECR Public/Debian mirrors and available Docker disk space |
| Containers run but site fails | Check application dependencies, local DB content, config and logs; container readiness is not CMS acceptance |
| Cannot lock project state | Another lifecycle command may be running; wait for it to finish |
| Incompatible/incomplete state | Preserve state and investigate; do not delete DB credentials as a repair shortcut |
An interrupted process can leave <project-id>.lock next to the state directory.
Only remove that specific lock after confirming no Silo lifecycle process for
the project is still active. Do not remove the environment directory or database
volume to clear a lock.
For deeper diagnostics, use Docker directly with the exact paths printed by Silo. For example, in PowerShell:
$state = 'C:\Users\you\.silo\projects\PROJECT_ID'
docker compose --project-name silo-PROJECT_ID --project-directory $state --env-file "$state\environment" -f "$state\compose.yaml" logs --tail 100 web dbReplace PROJECT_ID in both places. Avoid sharing docker compose config output
without review: its resolved configuration can contain passwords. Silo's own
startup validation uses config --quiet.
The CLI uses the Go standard library only; there is no go.sum until external
module dependencies are introduced.
go test ./...
go vet ./...
go build -o ./bin/silo ./cmd/siloThe opt-in Docker integration test creates its own temporary Evolution-shaped PHP fixture. It does not read or run an existing site. It builds the runtime, tests HTTP/rewrite, PHP extensions, one-time backup restore, live port changes, MariaDB persistence and two projects running concurrently without changing their fixture files. It removes only its own containers and database volumes after the test. Downloaded images/build cache remain available.
SILO_INTEGRATION=1 go test ./internal/cli -run '^TestDocker' -v -timeout 30mPowerShell equivalent:
$env:SILO_INTEGRATION = '1'
try {
go test ./internal/cli -run '^TestDocker' -v -timeout 30m
} finally {
Remove-Item Env:SILO_INTEGRATION
}The first image build may take several minutes. An available local port is selected for the fixture; the real application's default port is not used.
See architecture for boundaries and source research, and validation for the checks actually performed.
Implemented: project discovery, external state, PHP 8.4/Apache, MariaDB 11.4, diagnostics, lifecycle commands, saved-port changes, first database backup restore and simultaneous HTTP/HTTPS with local certificates and isolated test coverage.
Planned separately: additional runtimes, nginx/FrankenPHP/Xdebug, service
selection, shell/PHP/Composer/Node/database command proxies, Podman, GHCR
publishing, release binaries, installers, self-update, broader state reconfiguration
and optional project configuration. .silo.yml is not read by this version.
Silo takes runtime lessons from Salo2 and CLI inspiration from Laravel Sail, while keeping infrastructure outside the application.
MIT.