A field report on deploying Nextcloud in a staging environment managed with Ansible and Docker.
As part of restructuring my infrastructure, I recently set up a staging environment separate from production. The goal was simple: validate new application versions before deploying them to production.
Most of my applications follow the same pattern:
This approach works perfectly for:
For this, I created a generic Ansible role named:
staging_docker_app
This role simply performs:
For simple applications, this approach is efficient, reusable and easy to maintain.
Nextcloud, however, quickly exposed the limits of this method.
At first, I assumed Nextcloud could be deployed like any other application.
The configuration looked like this:
app_name: nextcloud
app_image: ghcr.io/sepp67/ansible-role-nextcloud-stack:latest
app_container_name: nextcloud-staging
app_host_port: 18080
app_container_port: 80
The deployment appeared to succeed.
The container started.
The proxy worked.
But the application wasn't actually operational.
Unlike a typical web application, Nextcloud isn't made up of a single service.
My deployment is composed of:
Nextcloud Stack
├── Nextcloud
├── PostgreSQL
├── Redis
└── Cron
The Docker Compose file makes this clear:
services:
db:
image: postgres
redis:
image: redis
app:
image: nextcloud
cron:
image: nextcloud
The main container depends on:
Starting only the Nextcloud container therefore leads to an incomplete deployment.
My generic role relied on the assumption:
One application = One container
Nextcloud follows a different model:
One application = Several cooperating services
This is a completely different deployment pattern.
Trying to handle both models in the same role quickly leads to:
At that point, the generic role stops being genuinely generic.
Rather than complicating the existing role, I created a dedicated one:
staging_nextcloud_stack
Responsibilities are now clearly separated.
staging_docker_app
Used for:
Responsibilities:
staging_nextcloud_stack
Responsibilities:
Another important lesson concerns reuse.
My infrastructure already had a role:
docker_host
responsible for installing:
At first, I had tried installing Docker directly from the Nextcloud role.
This created redundant responsibilities.
The final architecture became:
docker_host
│
▼
staging_nextcloud_stack
The Docker role manages Docker.
The Nextcloud role manages Nextcloud.
The staging environment now looks like this:
vm-proxy-staging
│
├── lavallee.staging.local
├── facturier.staging.local
├── grav.staging.local
├── webcam.staging.local
└── nextcloud.staging.local
Applications are deployed on dedicated virtual machines.
The Nextcloud VM contains:
vm-nextcloud-staging
│
├── PostgreSQL
├── Redis
├── Nextcloud
└── Cron
The whole thing is deployed automatically with Ansible and Docker Compose.
The goal of automation isn't to make every deployment identical.
The goal is to make every deployment predictable, maintainable and understandable.
For simple applications, a generic role is perfectly suited.
For applications made of several services, like Nextcloud, a dedicated role produces a cleaner, more durable architecture.
The lesson is simple:
Generic roles should stay generic. Complex applications deserve their own deployment logic.