Skip to main content
This page is unlisted and can only be accessed directly via URL. It is excluded from the site navigation and search results.

Migrate from Docker Compose to Kubernetes

Retool has previously supported Docker Compose for production deployments. Retool 4.0 introduces service requirements that Docker Compose cannot reliably meet in production: the agent sandbox requires resources and isolation beyond what a single-VM setup provides. Helm on Kubernetes is Retool's officially supported production deployment configuration.

This guide covers deploying a new blueprints instance, migrating your database, and cutting over DNS. Docker Compose remains supported for non-production and development use. For in-place upgrade guidance, refer to Upgrade your Docker Compose deployment.

What changed from your Docker Compose deployment

ChangeDetails
Agent sandbox requires KubernetesThe agent sandbox uses gVisor for process isolation and a custom seccomp profile. These requirements cannot be met on a single-VM Docker Compose setup.
Blob storage is now requiredObject storage (S3, Azure Blob, or GCS) is a platform requirement for app storage, Git repository storage, and sandbox snapshots. The bundled MinIO container is only supported for development use.
Helm values use rr.* namespaceComponents associated with the new app builder are configured under rr.enabled: true in Helm values. The old Docker Compose service names (jsExecutor, r2Agent, etc.) do not map to Helm top-level keys.
Same-origin networkingNo wildcard DNS or extra TLS certificate required. The sandbox routes through your existing Retool ingress.
Temporal is managed, not bundledRetool-managed Temporal is the default. There is no temporal.yaml equivalent in the Helm chart.

Before you start

Use this checklist as you prepare the migrate to ensure you have everything ready.

1. Collect your Docker Compose configuration

Before deploying the target instance, collect the configuration values you'll need to carry over.

Environment variables

Your Retool environment variables are stored in docker.env in your deployment directory. Open the file and note these values:

  • ENCRYPTION_KEY (required): must match exactly on the new deployment or your database dump will be unreadable.
  • LICENSE_KEY.
  • BASE_DOMAIN or your Retool domain.
  • SSO/OIDC/SAML settings (CLIENT_ID, CLIENT_SECRET, SSO_*, OIDC_*, SAML_*).
  • Any other non-default variables (SMTP, proxy settings, custom feature flags, etc.).

Scaling configuration

Note the CPU and memory limits set for each service in docker-compose.yml, and note your VM's total CPU and memory. You'll use this to size the target deployment appropriately.

Database connection details

In the default Docker Compose configuration, PostgreSQL runs as a container named postgres. If you've configured an external database, find the connection details in docker.env under POSTGRES_HOST, POSTGRES_PORT, POSTGRES_DB, and POSTGRES_USER.

2. Audit active features

Your deployment may use features that require extra steps or precautions during migration. Review these before you start:

  • Workflows or Agents: disable them on the source at the start of the code-freeze window to prevent scheduled jobs from running on both instances simultaneously. Refer to step 9 for when to do this.
  • Retool Database: refer to section 6 after restoring your main database.
  • Retool Storage: refer to section 7 after restoring your main database.

3. Deploy the target blueprints instance

Follow the Terraform blueprints guide to stand up a new blueprints instance. Before applying Terraform, configure these migration-specific settings:

  1. Set ENCRYPTION_KEY to match your source deployment. Store it in your cloud's secrets manager and reference it via encryption_key_secret_name. If this value doesn't match, your restored database will be unreadable.

  2. Set the Retool version to match or exceed your source. Pin the image tag via retool_helm_extra_values.

  3. Set all custom environment variables via retool_helm_extra_values, except SSO/OIDC/SAML settings. Leave DISABLE_USER_PASS_LOGIN unset (or false) so you can log in as a local admin to validate the instance before cutover.

  4. Match your scaling configuration. Set pod CPU, memory, and replica counts to match or exceed your source deployment.

  5. Set domain_name to your current Retool domain. This creates a wildcard TLS certificate for it and enables a temporary blueprints.<your-domain> subdomain for pre-cutover testing.

  6. If using Workflows or Agents, set workflows_enabled = false to prevent double-firing during the migration window.

During terraform apply, the run pauses waiting for an SSL certificate to validate. Add the DNS validation records to your DNS provider while it waits — this includes the ACM-provided CNAME record and a TXT record if your DNS provider requires domain ownership verification. The apply resumes automatically once the certificate validates. The full apply takes 30–45 minutes.

Once complete, retrieve the new load balancer hostname and add a temporary CNAME in your DNS provider:

Get load balancer hostname
terraform output -json modules | jq -r '.["user-ingress"].alb_dns_name'
Temporary DNS record
blueprints.<your-domain>  →  <load-balancer-dns-name>

4. Dump the source database

How you run pg_dump depends on whether your PostgreSQL instance runs as a Docker container or as an external database.

Run pg_dump from within the running postgres container, then copy the dump out:

Dump the database
time docker exec $(docker compose ps -q postgres) pg_dump -Fc --no-owner --no-acl \
-U retool \
-d retool \
-f /tmp/retool.dump
Copy dump to local disk
docker cp $(docker compose ps -q postgres):/tmp/retool.dump ./retool.dump

If the container name differs in your setup, replace postgres with the service name defined in docker-compose.yml.

Common values: <db-name> is typically hammerhead_production or retool; <db-user> is typically retool or retool_admin. Confirm from docker.env.

5. Restore to the target database

The target database is in a private subnet within the Kubernetes cluster. Connect via a temporary pod.

Get the target database connection details:

Get database connection details
terraform output -json modules | jq '{host: .["db-main"].address, db: .["db-main"].name, user: .["db-main"].username, port: .["db-main"].port}'
kubectl get secret db-credentials -n default -o jsonpath='{.data.password}' | base64 -d

Update your kubeconfig for the target cluster:

Update kubeconfig
aws eks update-kubeconfig --name <cluster-name> --region <region>

Run a temporary pod with a matching PostgreSQL version:

Start a temporary pod
kubectl run tmp-psql --image=postgres:<version> -it --rm --restart=Never -- sh

In a second terminal, copy the dump into the pod:

Copy dump into the pod
kubectl cp retool.dump tmp-psql:/retool.dump

Inside the pod, set connection variables and restore:

DROP DATABASE … WITH (FORCE) requires PostgreSQL 13 or later. On PostgreSQL 12 or earlier, use SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'retool'; before dropping.

Restore the database
export PGHOST=<host> PGPORT=<port> PGUSER=<user> PGPASSWORD=<password>

# Drop and recreate the database
psql -d postgres -c "DROP DATABASE retool WITH (FORCE)" -c "CREATE DATABASE retool"

# Restore
time pg_restore --no-owner --no-acl -d retool /retool.dump

# Restart all deployments to reconnect pods and run any pending migrations
kubectl rollout restart deployment -n default

6. Migrate Retool Database

If your deployment does not use Retool Database, skip this section.

Retool Database is a separate PostgreSQL database from the main Retool application database. You must dump and restore it separately, then update the connection strings on the new deployment.

Dump the Retool Database from within the postgres container (or from your external database host):

Dump Retool Database
time docker exec $(docker compose ps -q postgres) pg_dump -Fc --no-owner --no-acl \
-U retool \
-d <retooldb-name> \
-f /tmp/retooldb.dump

docker cp $(docker compose ps -q postgres):/tmp/retooldb.dump ./retooldb.dump

Copy the dump into a temporary pod in the target cluster and restore it, following the same process as section 5:

Restore Retool Database
psql -d postgres -c "CREATE DATABASE retooldb"
time pg_restore --no-owner --no-acl -d retooldb /retooldb.dump

After restoring, update the Retool Database connection string in retool_helm_extra_values to point to the new database.

7. Migrate Retool Storage

If your deployment does not use Retool Storage, skip this section.

Retool Storage uses blob storage. The default Docker Compose configuration includes a bundled MinIO container, which stores blobs on the local VM disk. Before cutover, migrate this data to a durable cloud bucket.

Copy data from the MinIO container to your cloud storage bucket. First, identify the MinIO container name from docker-compose.yml, then use the MinIO CLI or aws s3 sync with a MinIO-compatible endpoint:

Sync MinIO data to cloud bucket
aws s3 sync s3://<minio-bucket> s3://<target-bucket> \
--endpoint-url http://localhost:9000 \
--profile minio

After syncing, update retool_helm_extra_values to point Retool Storage at the target bucket.

8. Validate

Once the restore is complete, log in to blueprints.<your-domain> as a local admin and verify:

  1. Encryption: go to Resources and open any resource. If the credentials are readable, your encryption key is correct. If fields show garbled or empty values, the ENCRYPTION_KEY on the new instance doesn't match the source.
  2. Functional parity: spot check critical apps and queries. Confirm users, permissions, and audit logs look correct. Workflows will be non-functional here since you disabled them.

If anything looks wrong, you can repeat the dump and restore without affecting the source deployment. Nothing done so far has modified your Docker Compose instance.

9. Cut over DNS

This is the live migration. Time it during a low-traffic window and communicate the maintenance period to users in advance.

  1. Designate a code-freeze window. The final database dump is a point-in-time snapshot. Any apps, workflows, or config changes made after the final dump will not carry over.

  2. Begin the freeze and notify users.

  3. If using Workflows or Agents, disable them on the source to prevent jobs from double-firing after the new instance comes up.

  4. Take the final database dump by repeating sections 4 and 5 (and sections 6 and 7 if applicable).

  5. Validate on blueprints.<your-domain> one more time.

  6. Cut over DNS. Update your DNS provider to point <your-domain> at the new load balancer. The blueprints Terraform creates a DNS zone with the correct records. The cleanest path is to delegate your domain to it by updating the NS record at your registrar:

    Get nameservers
    terraform output -json modules | jq -r '.["user-ingress"].zone_name_servers'
  7. Verify the cutover. Once DNS propagates, confirm traffic is reaching the new instance:

    Verify DNS propagation
    dig +short <your-domain>

    Verify SSO is working end-to-end.

  8. Re-enable Workflows. Set workflows_enabled = true and run terraform apply. Verify scheduled jobs are running on the new instance.

  9. End the code freeze and notify users.

Post-migration

Leave the Docker Compose deployment stopped but intact for at least a week. Don't decommission it until you're confident the new instance is stable. To roll back, point DNS back at the original load balancer and re-enable Workflows on the source.

Once the new deployment is confirmed healthy, shut down the Docker Compose stack and decommission the VM.