Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Silo

Standalone local development environments for Evolution CMS, powered by Docker.

cd /path/to/your/evolution-project
silo doctor
silo up -d

Silo 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.

Contents

Requirements

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 version

Use 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.

Build Silo

Clone the source and build a native executable. No global installation or administrator access is needed for the Silo binary.

Windows PowerShell

git clone https://github.com/evolution-cms/silo.git
cd silo
go build -o .\bin\silo.exe .\cmd\silo
.\bin\silo.exe version

Add 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 -d

Replace the sample executable and application paths with your actual paths.

Linux, macOS and WSL2

git clone https://github.com/evolution-cms/silo.git
cd silo
go build -o ./bin/silo ./cmd/silo
./bin/silo version

Add 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 -d

In 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.

First startup

  1. Start Docker and select Linux containers.
  2. Open a terminal in your existing Evolution application. For an existing site, place its SQL backup in assets/backup before first startup.
  3. Run silo doctor and resolve any [FAIL] checks.
  4. Run silo up -d.
  5. Open http://127.0.0.1:8080.
  6. Use silo ps to inspect services and silo down to 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 8087

Then 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.

Run several projects at once

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 18081

Each 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.

How Silo finds your project

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.

Commands

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 and HTTPS

HTTP remains supported on its saved port. To enable HTTPS as well:

silo up -d --port 18080 --https-port 18443

Both 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.

Trust the local certificate

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\Root

This 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.

Runtime and database

Web service

  • 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). Running silo up after 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.1 only.
  • 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.

Database service

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.

Automatic first database restore

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 .sql and .sql.gz files 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/USE directives 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.

Configuration and state

Optional Evolution example file

If core/custom/.env.docker.example exists, Silo reads only a literal DB_DATABASE value from it:

DB_DATABASE=my_local_site

Simple 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.

State directory

~/.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 -d

Use 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.

Troubleshooting

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 db

Replace 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.

Development and tests

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/silo

The 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 30m

PowerShell 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.

Current scope

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.

License

MIT.

About

Standalone local development environments for Evolution CMS, powered by Docker.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages