Rather than automating the installation of a single site, an architecture separating runtime, applications, persistent data and deployment mechanism — designed to control the lifecycle of multiple Grav sites without duplicating their operations.
The first need was fairly simple: automate the deployment of a Grav site used as internal documentation.
But this site had to keep evolving after deployment. Pages would be edited from the admin interface, images added, accounts created.
Redeploying the application could therefore never reset the site to its initial state and destroy data produced in operation.
A first boundary appeared:
The application must be replaceable. User data must survive.
Persistent content was therefore separated from the container's lifecycle.
Then other projects needed Grav sites. The question was no longer: How do I deploy this site? but:
How do I have a mechanism to deploy and maintain multiple Grav sites without duplicating the operational logic?
That's the moment a deployment problem became an architecture problem.
Content created in operation must survive a container being replaced, a redeployment, and a version change.
The shared mechanics must not be copied and adapted for every new project.
Runtime, application, data and deployment don't evolve at the same pace or for the same reasons. Their separation should reflect these lifecycles.
Rolling back to a previous version should use, as much as possible, the same mechanism as a normal deployment.
Secrets and sensitive configuration stay out of application images and versioned code.
Analyzing the first deployment, then the arrival of new projects, brought out four distinct domains: runtime, application / site, persistent data and deployment mechanism. Grouping them together just because they serve the same site would have created unnecessary coupling. The architecture therefore aims to make these boundaries explicit.
grav-runtime provides the shared technical foundation: Grav, PHP, Nginx and the contract expected by applications. It knows nothing about any particular business project.
projet-gites, projet-lavallee-website and future sites add only what's specific to them: theme, plugins, application configuration and initial content. Each application stays independently versioned.
ansible-role-grav-site centralizes the shared mechanics: preparing the instance, persistent volumes, configuration, secrets, startup, health checks, updates and rollback. The role stays focused on one instance per invocation.
Pages, images, accounts and other persistent data live independently of the application image. A new version can therefore replace the application without automatically resetting its production state.
The first deployment made it possible to formalize a fundamental rule: rebuildable elements belong to the application; data produced in operation must live outside the container.
Grav, PHP, Nginx and the shared dependencies were isolated into grav-runtime. Application projects no longer have to rebuild this technical layer.
The operational mechanics were grouped into ansible-role-grav-site: volumes, secrets, configuration, health checks, deployed version, update and rollback.
projet-gites, then lavallee.tech, made it possible to test the model against several real applications. The second site in particular exposed assumptions still tied to the first project — names, paths or instance properties — and triggered their generalization.
An application is only installed once. It then has to be operable for years. The model being sought is therefore no longer:
install
but:
deploy → update → rollback → migrate
This difference shapes the architecture. The runtime is versioned. Each application is versioned. Data persists independently. The deployment mechanism applies the desired state.
Rollback isn't designed as a separate emergency procedure. If a new version causes a problem, a previously known-stable version simply becomes the desired version again.
version 1.3.2
│
▼
deploy 1.4.0
│
issue
│
▼
target = 1.3.2
│
▼
same deployment mechanism
This keeps special procedures confined to exactly the moment they'd be riskiest: during an incident.
The first site solved a need. The second started testing the architecture. The next ones must be able to reuse the same base without multiplying the operational logic.
It's easy to call an architecture "reusable" while it still serves only one project. The second consumer reveals the assumptions that were actually specific to the first.
lavallee.tech is now itself deployed on this architecture. Its arrival made it possible to identify the last instance parameters that needed generalizing for the deployment role to stay an atomic, reusable component.
The next step is to use this same mechanism for a new technical documentation site. Reusability is therefore not treated as a declared property.
It has to be verified by new consumers.
The same base — grav-runtime + ansible-role-grav-site — will be reused without modification for this new consumer, the only real proof that an architecture is reusable.
Using open source software is only part of the problem. Infrastructure remains hard to control if its deployment depends on manual steps, if its data is mixed in with the application, or if nobody knows precisely which version is in production.
This architecture therefore aims to make explicit:
Open source is a means. Control of the system is the goal.
Content creation and editorial design stay specific to each application.
The deployment role doesn't manage DNS, reverse proxy, or TLS certificates. Those responsibilities belong to the shared infrastructure.
Backups are still necessary. Grav avoids running an extra database service, but the persistent files are production data and must be backed up.
The deployment role manages one instance per invocation. Orchestrating multiple sites is deliberately handled at a higher level.
Applications requiring complex business features, heavy traffic, or advanced data models may fall outside the natural scope of a flat-file CMS like Grav.
This separation wasn't born from a theoretical design. It results from the evolution of a concrete need: preserving the data of a first site, then understanding how to maintain multiple applications without duplicating their infrastructure.
Automated deployment is only part of the problem. The real question is how the system will be updated, diagnosed, backed up, restored, and handed off over time.
Let's talk about your constraints first →