How to migrate from Docker Compose to OpenTofu on Quake AI
How to migrate from Docker Compose to OpenTofu on Quake AI
Docker Compose defines multi-container applications on a single host. Moving to Quake AI with OpenTofu means shifting from container orchestration to infrastructure provisioning: the containers still run, but the underlying VMs, networks, and storage are now codified.
This guide covers the most common path: deploying a Docker Compose application on Quake AI compute instances provisioned and configured with OpenTofu and cloud-init.
Conceptual mapping#
| Docker Compose concept | Quake AI OpenTofu equivalent |
|---|---|
services: (container definitions) | openstack_compute_instance_v2 + cloud-init to install Docker and run containers |
ports: (published ports) | openstack_networking_secgroup_rule_v2 (security group rules) + floating IP |
volumes: (named volumes) | openstack_blockstorage_volume_v3 (persistent NVMe block storage) |
networks: (Docker networks) | openstack_networking_network_v2 + openstack_networking_subnet_v2 |
depends_on: (service ordering) | OpenTofu resource dependencies (implicit or depends_on) |
.env file | terraform.tfvars or environment variables |
The pattern: OpenTofu provisions, cloud-init configures#
OpenTofu creates the infrastructure (VMs, networks, volumes, security groups). Cloud-init runs on first boot to install Docker and start your containers. Your docker-compose.yml file travels unchanged; it runs inside the VM.
Example: WordPress + MySQL#
If your current docker-compose.yml looks like this:
services:
wordpress:
image: wordpress:latest
ports:
- "80:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
depends_on:
- db
db:
image: mysql:8
volumes:
- db_data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
db_data:The OpenTofu equivalent on Quake AI uses a compute instance with cloud-init. Attach security groups to the Neutron port (security_group_ids), not to the instance by name. See Authoring IaC templates.
data "openstack_images_image_v2" "ubuntu" {
name = "Ubuntu-24.04"
most_recent = true
}
resource "openstack_networking_secgroup_v2" "web" {
name = "wordpress-web"
description = "Ingress for WordPress and SSH"
}
resource "openstack_networking_port_v2" "app" {
network_id = openstack_networking_network_v2.main.id
security_group_ids = [openstack_networking_secgroup_v2.web.id]
}
resource "openstack_compute_instance_v2" "app" {
name = "wordpress-app"
flavor_name = "s1a.medium"
block_device {
uuid = data.openstack_images_image_v2.ubuntu.id
source_type = "image"
destination_type = "volume"
volume_size = 20
boot_index = 0
delete_on_termination = true
}
user_data = templatefile("cloud-init.yaml", {
db_password = var.db_password
})
network {
port = openstack_networking_port_v2.app.id
}
}
resource "openstack_blockstorage_volume_v3" "db_data" {
name = "wordpress-db-data"
size = 20
}
resource "openstack_compute_volume_attach_v2" "db_data" {
instance_id = openstack_compute_instance_v2.app.id
volume_id = openstack_blockstorage_volume_v3.db_data.id
}The cloud-init script installs Docker, mounts the volume, and starts the containers:
#cloud-config
package_update: true
packages:
- docker.io
- docker-compose-v2
write_files:
- path: /opt/app/docker-compose.yml
content: |
services:
wordpress:
image: wordpress:latest
ports:
- "80:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_PASSWORD: ${db_password}
depends_on:
- db
db:
image: mysql:8
volumes:
- /mnt/db_data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: ${db_password}
runcmd:
- mkfs.ext4 /dev/vdb || true
- mkdir -p /mnt/db_data
- mount /dev/vdb /mnt/db_data
- echo '/dev/vdb /mnt/db_data ext4 defaults 0 2' >> /etc/fstab
- systemctl enable docker
- systemctl start docker
- cd /opt/app && docker compose up -dMigration workflow#
1. Inventory your Compose services#
List every service, its image, ports, volumes, and environment variables. Identify which services need:
- Public access (floating IP + security group rules)
- Persistent storage (block volumes)
- Private communication only (internal network)
2. Choose your topology#
| Compose setup | Quake AI topology |
|---|---|
| All services on one host | Single VM with Docker Compose (simplest migration) |
| Services that need isolation | Separate VMs per service group, private network |
| Multiple application hosts | Edge reverse proxy with a floating IP and private backend addresses |
For most small-to-medium Compose stacks, a single VM is the right starting point. Scale to multiple VMs when you need isolation or redundancy.
3. Provision with OpenTofu#
Use a Quake AI template as your starting point:
- Single-app stack → Simple VM template + cloud-init
- Web + DB → WordPress + MySQL template
- Multi-tier → Full-Stack Application template
- Multiple application hosts → Edge Reverse Proxy template in front of private backends
4. Transfer your data#
For volume data, copy files from your current host to the Quake AI instance:
rsync -avz --progress /path/to/local/data user@FLOATING_IP:/mnt/db_data/For database dumps:
docker exec db mysqldump -u root -p YOUR_DB > dump.sql
scp dump.sql user@FLOATING_IP:/tmp/
ssh user@FLOATING_IP "docker exec -i db mysql -u root -p < /tmp/dump.sql"5. Update DNS#
Point your domain to the new floating IP. If you used a reverse proxy (Nginx, Caddy, Traefik) in your Compose setup, it runs unchanged inside the VM.
What changes, what stays the same#
| Aspect | Changes | Stays the same |
|---|---|---|
| Container runtime | (none) | Docker, same images, same docker-compose.yml |
| Infrastructure | Managed by OpenTofu instead of manual setup | (none) |
| Networking | OpenStack security groups instead of host firewall | Docker internal networking between containers |
| Storage | OpenStack block volumes instead of host directories | Docker volume mounts inside the VM |
| Deployment | tofu apply provisions the VM; cloud-init starts containers | docker compose up -d still runs your app |
See also#
- Migration Explorer: interactive service comparison across cloud providers
- WordPress + MySQL template
- How to get started with Infrastructure as Code on Quake AI
- IaC on Quake AI
- Infrastructure Templates
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.
Comparisons to third-party providers in this material reflect publicly documented behavior as of the validation date below. Pricing, quotas, service limits, and feature availability change frequently on every cloud. Verify provider-specific claims against the provider's own current documentation before relying on them for a procurement, architecture, or migration decision.
For the full policy, see Usage Guidelines.
Last validated: 07.07.2026
See Also
Terraform and OpenTofu on Quake AI
Prerequisite
How to migrate from AWS CloudFormation to OpenTofu on Quake AI
Shares: IaC, Terraform
How to migrate from Hetzner Cloud to Quake AI with OpenTofu
Shares: IaC, Terraform
How to migrate from Linode (Akamai) to Quake AI with OpenTofu
Shares: IaC, Terraform
How to migrate from Vultr to Quake AI with OpenTofu
Shares: IaC, Terraform