Skip to content

Repository files navigation

cloud-compose

Deploy Docker Compose projects to VMs. The repository root is a compatibility entrypoint for existing GCP callers. New callers should select exactly one Terraform entrypoint under providers/: providers/gcp, providers/do, or providers/linode. Each entrypoint loads only its target cloud provider. Existing Debian/Ubuntu hosts can consume the same runtime contract through the Ansible role or Salt formula.

The compatibility root is frozen to GCP compatibility fixes and will not gain new public features after 2027-02-01. Existing state remains supported; plan a reviewed move to providers/gcp before that date. Runtime behavior remains contract-tested across entrypoints while the compatibility address exists.

Template defaults live in templates/apps.json and are shared by Terraform, Ansible, and Salt. The default deployment shape is one app per VM or host; pass runtime.compose.projects when several apps should share the same machine. Terraform template presets include an exact, reviewed sitectl core/plugin release set. Override individual selectors with runtime.sitectl.package_versions when intentionally testing or promoting a different compatible release set.

Renovate tracks both the Compose tags and sitectl release tags in the registry. Its pull requests are promotion candidates: keep the complete compatible set together and require the hosted application smoke before merging them.

All presets pin sitectl v1.9.1 plus the latest reviewed application plugin. ISLE selects the libops/isle v1.3.1 template, ArchivesSpace selects v1.0.1, WordPress selects v1.1.1, and Drupal, OJS, Omeka Classic, and Omeka S select v1.2.1. Keep each preset's complete template and package set together when promoting an override.

GCP deployments have two Terraform ownership layers. Apply modules/gcp-foundation once per service project from a small, long-lived state; it owns required APIs, the Cloud Run service identity, the reusable least-privilege power roles, and any Shared VPC attachment and service-agent grants. Per-application states consume the role names and bind their service accounts only to their own Compute Engine instance. They must not recreate or destroy the singleton foundation. The runtime contracts cover the state boundary, Shared VPC setup, and Cloud Run Direct VPC egress requirements.

Production operators can require the provider-neutral runtime.disaster_recovery contract. Cloud Compose then hands complete database, application-file, and volume-topology coverage to a root-owned operator driver and accepts only an encrypted off-host receipt plus scheduled disposable restore proof. Storage credentials remain outside Terraform and the runtime environment. Same-disk logical dumps and provider snapshots are never presented as disaster recovery.

Requirements

NameVersion
terraform>= 1.3.0
cloudinit~> 2.3
google~> 7.0
time~> 0.14

Providers

No providers.

Modules

NameSourceVersion
gcp./modules/gcpn/a
managed_artifacts./modules/managed-artifactsn/a

Resources

No resources.

Inputs

NameDescriptionTypeDefaultRequired
nameDeployment name.stringn/ayes
cloud_providerCompatibility selector for the root GCP entrypoint. Use providers/do or providers/linode for other clouds.string"gcp"no
gcpGoogle Cloud infrastructure settings.
object({
project_id = optional(string, "")
project_number = optional(string, "")
region = optional(string, "us-east5")
zone = optional(string, "us-east5-b")

identity = optional(object({
vm_service_account_email = optional(string, "")
app_service_account_email = optional(string, "")
app_credentials_enabled = optional(bool, false)
}), {})

instance = optional(object({
machine_type = optional(string, "n4-standard-2")
os = optional(string, "cos-125-19216-220-185")
production = optional(bool, false)
}), {})

disks = optional(object({
type = optional(string, "hyperdisk-balanced")
data_size_gb = optional(number, 20)
docker_volumes_size_gb = optional(number, 50)
}), {})

network = optional(object({
create = optional(bool, true)
project_id = optional(string, "")
name = optional(string, "")
subnetwork = optional(string, "")
ip_cidr_range = optional(string, "10.42.0.0/24")
mtu = optional(number, 1460)
power_button_allowed_ips = optional(list(string), [])
power_button_ip_depth = optional(number)
ssh_ipv4 = optional(list(string), [])
ssh_ipv6 = optional(list(string), [])
}), {})

snapshots = optional(object({
enabled = optional(bool, true)
}), {})

overlay = optional(object({
source_instance = optional(string, "")
volume_names = optional(list(string), [])
}), {})

cloud_init = optional(object({
initcmd = optional(list(string), [])
runcmd = optional(list(string), [])
}), {})

artifact_registry = optional(object({
repository = optional(string, "")
location = optional(string, "us")
}), {})

power_management = optional(object({
enabled = optional(bool, false)
start_role = optional(string, "")
suspend_role = optional(string, "")
frontend = optional(object({
image = string
port = optional(number, 8080)
cpu = optional(string, "1000m")
memory = optional(string, "1Gi")
}), null)
}), {})

rollout = optional(object({
enabled = optional(bool, false)
release_url = optional(string, "")
release_sha256 = optional(string, "")
port = optional(number, 8081)
jwks_uri = optional(string, "")
jwt_audience = optional(string, "")
custom_claims = optional(string, "")
allowed_ipv4 = optional(list(string), ["10.0.0.0/8"])
}), {})
})
{}no
runtimeProvider-neutral compose/runtime settings.
object({
rootfs = optional(string, "")
rootfs_archive_url = optional(string, "")
rootfs_archive_sha256 = optional(string, "")
users = optional(map(list(string)), {})

disaster_recovery = optional(object({
required = optional(bool, false)
driver_path = optional(string, "/etc/cloud-compose/libexec/offhost-backup-driver")
}), {})

compose = optional(object({
primary = optional(string, "")
ingress_port = optional(number, 80)
ingress = optional(object({
letsencrypt = optional(bool, false)
bot_mitigation = optional(bool, false)
mode = optional(string, "")
domain = optional(string, "")
acme_email = optional(string, "")
trusted_ips = optional(list(string), [])
max_upload_size = optional(string, "")
upload_timeout = optional(string, "")
}), {})
repo = optional(string, "")
branch = optional(string, "")
projects = optional(map(object({
docker_compose_repo = string
docker_compose_branch = optional(string)
project_dir = optional(string)
compose_project_name = optional(string)
ingress_port = optional(number)
ingress = optional(object({
letsencrypt = optional(bool)
bot_mitigation = optional(bool)
mode = optional(string)
domain = optional(string)
acme_email = optional(string)
trusted_ips = optional(list(string))
max_upload_size = optional(string)
upload_timeout = optional(string)
}), {})
sitectl_context_name = optional(string)
sitectl_plugin = optional(string)
sitectl_environment = optional(string)
sitectl_packages = optional(list(string))
sitectl_verify_args = optional(list(string))
docker_compose_init = optional(list(string))
docker_compose_up = optional(list(string))
docker_compose_down = optional(list(string))
docker_compose_rollout = optional(list(string))
})), {})
init = optional(list(string))
up = optional(list(string))
down = optional(list(string))
rollout = optional(list(string))
}), {})

sitectl = optional(object({
packages = optional(list(string))
version = optional(string, "latest")
package_versions = optional(map(string), {})
context_name = optional(string, "")
plugin = optional(string, "core")
environment = optional(string, "production")
verify_args = optional(list(string), [])
}), {})

docker = optional(object({
# renovate: datasource=github-releases depName=docker-compose packageName=docker/compose versioning=semver
compose_version = optional(string, "v5.3.1")
# renovate: datasource=github-releases depName=docker-buildx packageName=docker/buildx versioning=semver
buildx_version = optional(string, "v0.35.0")
}), {})

managed_runtime = optional(object({
enabled = optional(bool, true)
internal_services_enabled = optional(bool, false)
internal_services_auto_update = optional(bool, false)
artifacts = optional(list(object({
name = string
url = string
sha256 = string
path = string
mode = optional(string, "0755")
owner = optional(string, "root")
group = optional(string, "root")
restart = optional(string, "")
})), [])
}), {})

vault = optional(object({
addr = optional(string, "")
namespace = optional(string, "")
role = optional(string, "")
agent_enabled = optional(bool, false)
auth_method = optional(string, "auto")
gcp_auth_mount_path = optional(string, "auth/gcp")
agent_token_path = optional(string, "/mnt/disks/data/vault/token")
agent_additional_config = optional(string, "")
agent_templates = optional(list(object({
destination = string
contents = string
perms = optional(string, "0640")
command = optional(string, "")
})), [])
}), {})

extra_env = optional(map(string), {})
})
{}no
templateOptional compose template preset. Supported values are archivesspace, ojs, isle, drupal, wp, omeka-s, and omeka-classic. Explicit runtime settings override preset defaults.string""no

Outputs

NameDescription
appGsaThe Google Service Account the app can use for app-scoped auth.
backendBackend service ID for attaching Cloud Run ingress to an external HTTPS load balancer.
cloud_providerRoot entrypoint cloud provider (gcp).
compose_projectsNormalized compose project manifest.
external_ipGCP VM public IPv4 address.
instanceGCP VM instance details.
instance_idGCP VM instance ID.
internal_ipGCP VM private IPv4 address.
networkResolved GCP network and regional subnetwork.
primary_compose_projectNormalized primary compose project.
rolloutOptional rollout API endpoint details.
serviceGsaThe Google Service Account internal services run as.
sitectl_package_versionsEffective release selector for every installed sitectl package; values may be exact tags or latest.
templateSelected compose template preset.
urlsCloud Run ingress URLs by region.
volumesGCP persistent application-data and Docker-volume details.

About

Deploy libops docker compose templates to the cloud or on-prem

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages