Skip to content

Nextcloud files and collaboration

Template

Nextcloud files and collaboration

This pattern composes Compute, Network, and Block Storage into a self-hosted file-sync and collaboration platform for a team, on infrastructure you control.

What this template does#

Provisions a single instance running Nextcloud, an open-source file-sync and collaboration platform (a self-hosted alternative to Dropbox or the file layer of Google Workspace). Your team stores, syncs, and shares files on infrastructure you own:

  • Compute instance that runs Nextcloud's app container in Docker alongside a bundled MariaDB and Redis (4 vCPU and 4 GiB RAM)
  • Private network, subnet, router, port, and security group; a floating IP for public access
  • A block volume mounted at /var/lib/docker, so the MariaDB data and the Nextcloud html/data directory live on a volume you can grow; the default volume size (40 GiB) is larger than the lighter ops-tools templates in this library because Nextcloud's entire purpose is storing user files and its footprint grows with usage
  • cloud-init installs Docker Engine, brings up MariaDB and Redis, and prepares Nextcloud to start once you finish configuration

The MariaDB root/app passwords and the Redis password are generated on first boot and written to /opt/nextcloud/.env; no credential ships with this template.

Nextcloud needs a trusted domain before it accepts requests#

Like Infisical, Plane, and Mattermost, Nextcloud requires configuration tied to your public domain before it is fully usable: it rejects requests for hosts not on NEXTCLOUD_TRUSTED_DOMAINS. This template holds the Nextcloud app container until you finish that configuration; MariaDB and Redis start immediately.

Parameters#

ParameterDescriptionDefault
key_nameSSH keypair name (must already exist)No default
flavor_nameInstance size (Nextcloud plus MariaDB and Redis runs on 4 vCPU / 4 GiB for a small team)s1a.medium
image_nameOperating system imageUbuntu-24.04
app_nameDisplay name prefix for resourcesnextcloud
volume_sizeBlock volume size in GiB, mounted at /var/lib/docker; grows with file storage usage40
external_networkExternal network for floating IP allocationPublicStatic
private_cidrCIDR for the private subnet10.57.0.0/24
app_allowed_cidrCIDR allowed to reach Nextcloud on port 808010.57.0.0/24

Finish setup after apply#

cloud-init starts MariaDB and Redis and writes the generated passwords to /opt/nextcloud/.env. Complete the setup over SSH:

  1. Point a domain's DNS A record at the floating IP and put a reverse proxy (Caddy or Nginx) in front for HTTPS on 443.
  2. Edit /opt/nextcloud/.env: uncomment and set NEXTCLOUD_ADMIN_USER, NEXTCLOUD_ADMIN_PASSWORD, NEXTCLOUD_TRUSTED_DOMAINS (your public hostname), OVERWRITEPROTOCOL=https, and OVERWRITECLIURL (your public HTTPS address).
  3. Start Nextcloud:
bash
cd /opt/nextcloud
sudo docker compose up -d
  1. Open the trusted domain and log in with the admin account you set in step 2.

Access and security#

Nextcloud listens on port 8080 over plain HTTP. The security group restricts 8080 to app_allowed_cidr, which defaults to the private network only. Because Nextcloud checks its trusted-domain list, the normal access path is a domain with HTTPS on 443 behind a reverse proxy. Ports 80 and 443 stay open for that proxy; they carry no traffic until you add one.

When to use this pattern#

Run file sync, sharing, and storage for a team on a host you operate. This template's default sizing suits a small team; storage usage grows as your team uploads files, so plan to grow volume_size over time. To run MariaDB separately from the start, point MYSQL_HOST in /opt/nextcloud/.env at a self-managed PostgreSQL-style dedicated database instance and remove the bundled db service from the compose file.

Estimated cost#

Monthly cost estimate

Pricing calculator ↗

Sized as a custom package on shared vCPU.

Starting template$33.60/mo

Monthly total for the required template above. Use the configurator below to add optional pieces and see the total update.

What each resource is for

Nextcloud files/collaboration host

s1a.medium · 4 shared vCPU, 4 GiB RAM, 0.5 Gbps

Runs Nextcloud's app container alongside a bundled MariaDB and Redis, with the database data and the Nextcloud html/data directory on an attached volume.

Nextcloud plus its bundled MariaDB and Redis runs on 4 vCPU and 4 GiB RAM for a small team. Nextcloud's entire purpose is storing user files, so its storage footprint grows with usage: plan to grow the attached volume over time.

$33.00/mo

Compute shown per role at custom-package rates ($29/dedicated vCPU, $7.25/shared vCPU, $1/GiB RAM). The headline above is the billed total: the cheaper of a named plan and the custom package, plus add-ons.

Included in baseline

s1a.medium

4 shared vCPU, 4 GiB RAM, 0.5 Gbps

$33.00

Compute + RAM rate basis

4 vCPU + 4 GiB RAM at $29/dedicated vCPU, $7.25/shared vCPU, $1/GiB RAM (regular). Totals apply the flat −$5/mo package promotion.

—

Block storage (70 GiB)

70 GiB at $0.08/GiB/mo

$5.60

Public IP (included)

1 included with the custom package

$0.00

Package promotional discount

Flat −$5.00/mo on the custom package (same promotion as named plans).

$-5.00

Included at no charge

These line items are zero on Quake AI. Many other providers meter them separately.

Data transfer (inbound and outbound)

Unlimited data transfer on every plan; Quake AI does not meter per-GB egress.

AWS, GCP, and Azure meter outbound transfer per GB. DigitalOcean and Hetzner include an allowance on compute plans, then charge overage.

Learn more
$0.00

Private networking

Private networks, subnets, Neutron routers, and security groups are included with the plan.

VPC objects are usually free to create elsewhere, but NAT gateways bill hourly plus per-GB processed. Quake AI uses router SNAT with no separate NAT line item.

$0.00

Control-plane API requests

OpenStack API calls for provisioning and management are included.

Some managed services on other clouds meter API calls or charge for premium control-plane features.

$0.00

Pricing data last validated: . For current rates, check quake.ai/pricing.

Template source#

7 files. Download the zip or expand to copy any file.Download nextcloud-files.zip
Show source (7 files)
main.tfHCL
data "openstack_images_image_v2" "os" {
  name        = var.image_name
  most_recent = true
}

data "openstack_networking_network_v2" "external" {
  name = var.external_network
}

resource "openstack_networking_network_v2" "private" {
  name           = "${var.app_name}-net"
  admin_state_up = true
}

resource "openstack_networking_subnet_v2" "private" {
  name            = "${var.app_name}-subnet"
  network_id      = openstack_networking_network_v2.private.id
  cidr            = var.private_cidr
  ip_version      = 4
  dns_nameservers = ["1.1.1.1", "8.8.8.8"]
}

resource "openstack_networking_router_v2" "main" {
  name                = "${var.app_name}-router"
  external_network_id = data.openstack_networking_network_v2.external.id
}

resource "openstack_networking_router_interface_v2" "private" {
  router_id = openstack_networking_router_v2.main.id
  subnet_id = openstack_networking_subnet_v2.private.id
}

resource "openstack_networking_secgroup_v2" "nextcloud" {
  name        = "${var.app_name}-sg"
  description = "SSH and HTTP/HTTPS for a reverse proxy; app port 8080 restricted"
}

resource "openstack_networking_secgroup_rule_v2" "ssh" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 22
  port_range_max    = 22
  remote_ip_prefix  = "0.0.0.0/0"
  security_group_id = openstack_networking_secgroup_v2.nextcloud.id
}

# 80 and 443 carry Nextcloud once it is served over a domain with automatic
# TLS through a reverse proxy (Caddy or Nginx). Nextcloud rejects requests
# for hosts not on NEXTCLOUD_TRUSTED_DOMAINS, so the app stays on port 8080
# until you finish that setup.
resource "openstack_networking_secgroup_rule_v2" "http" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 80
  port_range_max    = 80
  remote_ip_prefix  = "0.0.0.0/0"
  security_group_id = openstack_networking_secgroup_v2.nextcloud.id
}

resource "openstack_networking_secgroup_rule_v2" "https" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 443
  port_range_max    = 443
  remote_ip_prefix  = "0.0.0.0/0"
  security_group_id = openstack_networking_secgroup_v2.nextcloud.id
}

# Raw app HTTP on 8080 is restricted to app_allowed_cidr (the private network
# by default). Use it for setup over an SSH tunnel or a scoped workstation IP;
# put a reverse proxy on 443 in front for routine access.
resource "openstack_networking_secgroup_rule_v2" "app" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 8080
  port_range_max    = 8080
  remote_ip_prefix  = var.app_allowed_cidr
  security_group_id = openstack_networking_secgroup_v2.nextcloud.id
}

resource "openstack_networking_port_v2" "nextcloud" {
  name               = "${var.app_name}-port"
  network_id         = openstack_networking_network_v2.private.id
  security_group_ids = [openstack_networking_secgroup_v2.nextcloud.id]

  fixed_ip {
    subnet_id = openstack_networking_subnet_v2.private.id
  }

  depends_on = [openstack_networking_router_interface_v2.private]
}

resource "openstack_blockstorage_volume_v3" "data" {
  name = "${var.app_name}-data"
  size = var.volume_size
}

resource "openstack_compute_instance_v2" "nextcloud" {
  name        = var.app_name
  flavor_name = var.flavor_name
  key_pair    = var.key_name

  user_data = templatefile("${path.module}/cloud-init/nextcloud.yaml.tftpl", {
    app_name = var.app_name
  })

  block_device {
    uuid                  = data.openstack_images_image_v2.os.id
    source_type           = "image"
    destination_type      = "volume"
    volume_size           = 30
    boot_index            = 0
    delete_on_termination = true
  }

  network {
    port = openstack_networking_port_v2.nextcloud.id
  }
}

resource "openstack_compute_volume_attach_v2" "data" {
  instance_id = openstack_compute_instance_v2.nextcloud.id
  volume_id   = openstack_blockstorage_volume_v3.data.id
}

resource "openstack_networking_floatingip_v2" "nextcloud" {
  pool = var.external_network
}

resource "openstack_networking_floatingip_associate_v2" "nextcloud" {
  floating_ip = openstack_networking_floatingip_v2.nextcloud.address
  port_id     = openstack_networking_port_v2.nextcloud.id
}
variables.tfHCL
variable "key_name" {
  description = "SSH keypair name (must already exist in your project)"
  type        = string
}

variable "flavor_name" {
  description = "Instance size. Nextcloud's app container plus bundled MariaDB and Redis runs on 4 vCPU and 4 GiB RAM (s1a.medium) for a small team. Size up as user count and file volume grow."
  type        = string
  default     = "s1a.medium"
}

variable "image_name" {
  description = "Operating system image. Ubuntu 24.04 is the recommended base."
  type        = string
  default     = "Ubuntu-24.04"
}

variable "app_name" {
  description = "Display name prefix for compute and network resources"
  type        = string
  default     = "nextcloud"
}

variable "volume_size" {
  description = "Block volume size in GiB, mounted at /var/lib/docker so the MariaDB data and Nextcloud's /var/www/html data directory live on a volume you can grow rather than on the boot disk. Defaults larger than the lighter ops-tools templates in this library because Nextcloud's entire purpose is storing user files: its footprint grows with usage, so plan to grow this volume over time."
  type        = number
  default     = 40
}

variable "external_network" {
  description = "Shared external network for router gateway and floating IPs; defaults to PublicStatic (persisted FIP / production pattern). Override with PublicEphemeral for ephemeral demos."
  type        = string
  default     = "PublicStatic"
}

variable "private_cidr" {
  description = "CIDR for the private tenant network the instance lives in"
  type        = string
  default     = "10.57.0.0/24"
}

variable "app_allowed_cidr" {
  description = "CIDR allowed to reach Nextcloud on port 8080. Defaults to the private network only, so the app is not exposed to the public internet on its raw port. Put a reverse proxy on 443 in front for HTTPS once you point a domain at the instance. To reach port 8080 directly from your workstation during setup, set this to YOUR_IP/32."
  type        = string
  default     = "10.57.0.0/24"
}
outputs.tfHCL
output "instance_id" {
  description = "ID of the compute instance running Nextcloud"
  value       = openstack_compute_instance_v2.nextcloud.id
}

output "floating_ip" {
  description = "Public floating IP address of the Nextcloud host"
  value       = openstack_networking_floatingip_v2.nextcloud.address
}

output "private_ip" {
  description = "Private IP address of the instance"
  value       = openstack_compute_instance_v2.nextcloud.access_ip_v4
}

output "app_url" {
  description = "Nextcloud app URL on port 8080. Reachable from app_allowed_cidr (the private network by default). cloud-init starts only the database and cache; set NEXTCLOUD_ADMIN_USER, NEXTCLOUD_ADMIN_PASSWORD, and NEXTCLOUD_TRUSTED_DOMAINS in /opt/nextcloud/.env and start the app container yourself, mirroring the Mattermost and Plane setup pattern."
  value       = "http://${openstack_networking_floatingip_v2.nextcloud.address}:8080"
}
versions.tfHCL
terraform {
  required_version = ">= 1.6.0"

  required_providers {
    openstack = {
      source  = "terraform-provider-openstack/openstack"
      version = "~> 2.0"
    }
  }
}

provider "openstack" {}
terraform.tfvars.exampleHCL
# Required: SSH keypair must already exist in your project
key_name = "YOUR_KEY_NAME"

# Recommended: restrict the app port (8080) to your workstation IP for setup.
# Leave unset to keep 8080 reachable only from the private network and tunnel
# over SSH. For production use, put a reverse proxy in front on 443.
# app_allowed_cidr = "203.0.113.10/32"

# flavor_name = "s1a.medium"
# image_name = "Ubuntu-24.04"
# app_name = "nextcloud"
# volume_size = 40
# external_network = "PublicStatic"
# private_cidr = "10.57.0.0/24"
cloud-init/nextcloud.yaml.tftpl
#cloud-config
package_update: true
packages:
  - ca-certificates
  - curl
write_files:
  - path: /opt/nextcloud/docker-compose.yml
    permissions: "0644"
    content: |
      # Nextcloud files/collaboration for ${app_name}. The app listens on
      # port 8080. Nextcloud rejects requests for hosts not on
      # NEXTCLOUD_TRUSTED_DOMAINS, so set that (and the admin bootstrap
      # credentials) in /opt/nextcloud/.env before starting the app. No
      # credential ships with this template: the MariaDB and Redis passwords
      # are generated on first boot. cloud-init starts only db and redis;
      # bring up the app after you finish configuring /opt/nextcloud/.env.
      services:
        app:
          image: nextcloud:apache
          restart: unless-stopped
          ports:
            - "8080:80"
          env_file:
            - /opt/nextcloud/.env
          volumes:
            - nextcloud_html:/var/www/html
          depends_on:
            - db
            - redis
        db:
          image: mariadb:lts
          restart: unless-stopped
          command: --transaction-isolation=READ-COMMITTED
          env_file:
            - /opt/nextcloud/.env
          volumes:
            - nextcloud_db:/var/lib/mysql
        redis:
          image: redis:alpine
          restart: unless-stopped
          command: sh -c "exec redis-server --requirepass \"$$REDIS_HOST_PASSWORD\""
          env_file:
            - /opt/nextcloud/.env
      volumes:
        nextcloud_html:
        nextcloud_db:
runcmd:
  - |
    set -e
    # The data volume attaches as /dev/sdb on this platform (not /dev/vdb).
    # Mount it at /var/lib/docker before Docker is installed so the MariaDB
    # data and the Nextcloud html/data directory live on the resizable
    # volume rather than the boot disk.
    DEV=/dev/sdb
    for i in $(seq 1 30); do [ -b "$DEV" ] && break; sleep 5; done
    if ! blkid "$DEV" >/dev/null 2>&1; then mkfs.ext4 -F -L nextclouddata "$DEV"; fi
    mkdir -p /var/lib/docker
    mount "$DEV" /var/lib/docker
    grep -q "$DEV" /etc/fstab || echo "$DEV /var/lib/docker ext4 defaults,nofail 0 2" >> /etc/fstab
    # Install Docker Engine plus the compose plugin from Docker's convenience
    # script.
    curl -fsSL https://get.docker.com | sh
    # Generate the bundled MariaDB root/app passwords and the Redis password
    # on first boot. They never leave this instance.
    DBROOTPASS=$(openssl rand -hex 24)
    DBPASS=$(openssl rand -hex 24)
    REDISPASS=$(openssl rand -hex 24)
    umask 077
    {
      echo "MYSQL_ROOT_PASSWORD=$DBROOTPASS"
      echo "MYSQL_DATABASE=nextcloud"
      echo "MYSQL_USER=nextcloud"
      echo "MYSQL_PASSWORD=$DBPASS"
      echo "MYSQL_HOST=db"
      echo "REDIS_HOST=redis"
      echo "REDIS_HOST_PASSWORD=$REDISPASS"
      echo "# Set the admin bootstrap account and your public HTTPS domain"
      echo "# before starting the app:"
      echo "# NEXTCLOUD_ADMIN_USER=admin"
      echo "# NEXTCLOUD_ADMIN_PASSWORD=change-me"
      echo "# NEXTCLOUD_TRUSTED_DOMAINS=files.example.com"
      echo "# OVERWRITEPROTOCOL=https"
      echo "# OVERWRITECLIURL=https://files.example.com"
    } > /opt/nextcloud/.env
    chmod 600 /opt/nextcloud/.env
    # Bring up the database and cache only. The app starts after you set
    # NEXTCLOUD_ADMIN_USER, NEXTCLOUD_ADMIN_PASSWORD, and
    # NEXTCLOUD_TRUSTED_DOMAINS in /opt/nextcloud/.env and run:
    #   cd /opt/nextcloud && docker compose up -d
    cd /opt/nextcloud
    docker compose up -d db redis
README.mdMarkdown
# Nextcloud files and collaboration

Single compute instance running [Nextcloud](https://nextcloud.com), a self-hosted file-sync and collaboration platform (a self-hosted alternative to Dropbox or Google Workspace's file layer) on infrastructure you control. After apply, cloud-init starts the bundled MariaDB and Redis; you set the admin bootstrap account and trusted domain and start the app container yourself.


**Network class:** production — `external_network` defaults to `PublicStatic` for persisted floating IPs and multi-tier stacks; override with `PublicEphemeral` for ephemeral demos.

The instance provisions a private network, a floating IP, and a block volume mounted at `/var/lib/docker` so the MariaDB data and the Nextcloud html/data directory live on a resizable volume.

## Where this fits

Nextcloud centralizes file storage, sync, and sharing for a team, with calendar, contacts, and collaborative document apps available as add-ons, on infrastructure you own rather than on a third-party SaaS.

## Resource floor: sized for a small team, storage grows with usage

Nextcloud's app server plus its bundled MariaDB and Redis need more headroom than a single-process tool. This template's `s1a.medium` default (4 vCPU, 4 GiB RAM) suits a small team. The `volume_size` default (40 GiB) is larger than the lighter ops-tools templates in this library because Nextcloud's entire purpose is storing user files: the attached volume fills up as your team uploads data, so plan to grow it over time rather than treating the default as a ceiling.

## Prerequisites

- OpenTofu >= 1.6.0 or Terraform >= 1.6.0
- Quake AI account with OpenStack credentials
- An existing SSH keypair in your project (the value of `key_name` must match that keypair)

## Usage

1. Clone or copy this template directory
2. Copy `terraform.tfvars.example` to `terraform.tfvars` and fill in your values
3. Source your OpenStack credentials: `source openrc.sh`
4. Initialize: `tofu init`
5. Preview: `tofu plan`
6. Apply: `tofu apply`

After apply, cloud-init installs Docker, generates the MariaDB root/app passwords and the Redis password into `/opt/nextcloud/.env`, and starts only `db` and `redis`. No credential ships with this template: every password is generated on first boot.

## Finish setup after apply

Nextcloud rejects requests for hosts not on its trusted-domain list, so cloud-init holds the app container until you finish configuration:

1. Point a domain's DNS A record at `floating_ip` and put a reverse proxy (Caddy or Nginx) in front for HTTPS on 443.
2. Edit `/opt/nextcloud/.env`: uncomment and set `NEXTCLOUD_ADMIN_USER`, `NEXTCLOUD_ADMIN_PASSWORD`, `NEXTCLOUD_TRUSTED_DOMAINS` (your public hostname), `OVERWRITEPROTOCOL=https`, and `OVERWRITECLIURL` (your public HTTPS address), so Nextcloud generates correct links when served over HTTPS through a reverse proxy rather than terminating TLS itself.
3. Start Nextcloud:

```bash
cd /opt/nextcloud
sudo docker compose up -d
```

4. Open the trusted domain and log in with the admin account you set in step 2.

## Access and security

Nextcloud listens on port 8080 over plain HTTP. The security group restricts 8080 to `app_allowed_cidr`, which defaults to the private network only. Ports 80 and 443 stay open for a reverse proxy you add for production use; they carry no traffic until you add one.

## Datastores

This template bundles MariaDB and Redis as containers on the same instance, which suits a single-team deployment. To run MariaDB as a separate service, point `MYSQL_HOST` in `/opt/nextcloud/.env` at a dedicated database instance and remove the bundled `db` service from the compose file.

## Variables

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `key_name` | string | yes | n/a | SSH keypair name (must already exist in your project) |
| `flavor_name` | string | no | `s1a.medium` | Instance size (Nextcloud plus MariaDB and Redis runs on 4 vCPU / 4 GiB) |
| `image_name` | string | no | `Ubuntu-24.04` | Operating system image |
| `app_name` | string | no | `nextcloud` | Display name prefix for resources |
| `volume_size` | number | no | `40` | Block volume size in GiB, mounted at `/var/lib/docker`; grows with file storage usage |
| `external_network` | string | no | `PublicStatic` | Persisted FIP / production default; override with `PublicEphemeral` for demos |
| `private_cidr` | string | no | `10.57.0.0/24` | CIDR for the private subnet |
| `app_allowed_cidr` | string | no | `10.57.0.0/24` | CIDR allowed to reach Nextcloud on port 8080 |

## Outputs

| Name | Description |
| --- | --- |
| `floating_ip` | Public floating IP assigned to the instance |
| `private_ip` | Private IP address of the instance |
| `app_url` | Nextcloud app URL on port 8080 |
| `instance_id` | Compute instance ID |

## Scope

This is a single-VM Nextcloud host that you operate, not a managed multi-tenant file-storage service. It is CPU-only, runs in one region, and bundles MariaDB and Redis as containers on the same host, sized for a small team. You operate the instance, Docker, Nextcloud, the database, and the data volume yourself: back them up, patch them, and grow the attached volume as your team's file storage usage grows.

## Documentation

See also: [self-managed PostgreSQL](/resources/iac-templates/self-managed-postgres), [Mattermost team chat](/resources/iac-templates/mattermost-team-chat), [Infisical secrets management](/resources/iac-templates/infisical-secrets)
Resources, parameters, and variables
Provisions
Parameterized by
Variables
  • key_namerequired
  • flavor_name="s1a.medium"
  • image_name="Ubuntu-24.04"
  • app_name="nextcloud"
  • volume_size=40
  • external_network="PublicStatic"
  • private_cidr="10.57.0.0/24"
  • app_allowed_cidr="10.57.0.0/24"

Customize this pattern#

See also#

Usage Guidelines

The sample code, software libraries, command line tools, proofs of concept, templates, and other related technology on this page (including any of the foregoing that is provided by Quake AI personnel) is provided to you as Quake AI Content under the Quake AI Customer Agreement, or the relevant written agreement between you and Quake AI (whichever applies). Do not use this Quake AI Content in your production accounts, or on production or other critical data. You are responsible for testing, securing, and optimizing the Quake AI Content (such as sample code) as appropriate for production grade use based on your specific quality control practices and standards. Deploying Quake AI Content may incur Quake AI charges for creating or using Quake AI chargeable resources, such as running Compute instances or storing data in Object Storage. Your use is also subject to the Acceptable Use Policy.

For the full policy, see Usage Guidelines.

Was this page helpful?