feat: add redis migration script (#6575)

Co-authored-by: Matti Nannt <matti@formbricks.com>
Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com>
This commit is contained in:
Victor Hugo dos Santos
2025-09-22 08:18:02 -03:00
committed by GitHub
parent 5d53ed76ed
commit 1557ffcca1
4 changed files with 469 additions and 182 deletions

View File

@@ -45,7 +45,7 @@ external_s3_guard() {
if ! has_service minio; then
print_warning "Detected existing S3 credentials in docker-compose.yml and no bundled MinIO service."
print_error "This migration is intended only for setups using local uploads."
print_info "No changes were made. If you already use external S3 (incl. AWS), you don't need this migration."
print_info "No files were migrated. If you already use external S3 (incl. AWS), you don't need this migration."
exit 0
fi
fi
@@ -287,7 +287,8 @@ has_service() {
}
add_or_replace_env_var() {
local key="$1"; local value="$2"
local key="$1"; local value="$2"; local section="${3:-STORAGE}"
if grep -q "^[[:space:]]*$key:" docker-compose.yml; then
# Replace existing uncommented key
if sed --version >/dev/null 2>&1; then sed -i "s|^\([[:space:]]*$key:\).*|\1 \"$value\"|" docker-compose.yml; else sed -i '' "s|^\([[:space:]]*$key:\).*|\1 \"$value\"|" docker-compose.yml; fi
@@ -295,29 +296,77 @@ add_or_replace_env_var() {
# Uncomment placeholder and set
if sed --version >/dev/null 2>&1; then sed -i "s|^[[:space:]]*#[[:space:]]*$key:.*| $key: \"$value\"|" docker-compose.yml; else sed -i '' "s|^[[:space:]]*#[[:space:]]*$key:.*| $key: \"$value\"|" docker-compose.yml; fi
else
# Append into STORAGE section before OAUTH header if present
awk -v insert_key="$key" -v insert_val="$value" '
BEGIN{printed=0}
/################################################### OPTIONAL \(STORAGE\) ###################################################/ {print; in_storage=1; next}
in_storage && /############################################# OPTIONAL \(OAUTH CONFIGURATION\) #############################################/ && !printed { print " " insert_key ": \"" insert_val "\""; printed=1; print; in_storage=0; next }
{ print }
END { if(in_storage && !printed) print " " insert_key ": \"" insert_val "\"" }
' docker-compose.yml > tmp.yml && mv tmp.yml docker-compose.yml
# Add to specified section with fallback
local section_found=false
if [[ "$section" == "REQUIRED" ]] && grep -q -E "^[[:space:]]*#+[[:space:]]*REQUIRED[[:space:]]*#+[[:space:]]*$" docker-compose.yml; then
# Add to REQUIRED section
awk -v insert_key="$key" -v insert_val="$value" '
/^[[:space:]]*#+[[:space:]]*REQUIRED[[:space:]]*#+[[:space:]]*$/ {print; in_required=1; next}
in_required && /^[[:space:]]*#+.*OPTIONAL/ && !printed {
print ""; print " # " insert_key " (required for Formbricks 4.0)";
print " " insert_key ": \"" insert_val "\""; printed=1
}
{ print }
END { if(in_required && !printed) { print ""; print " # " insert_key " (required for Formbricks 4.0)"; print " " insert_key ": \"" insert_val "\"" } }
' docker-compose.yml > tmp.yml && mv tmp.yml docker-compose.yml
section_found=true
elif [[ "$section" == "STORAGE" ]] && grep -q -E "^[[:space:]]*#+[[:space:]]*OPTIONAL[[:space:]]*\\([[:space:]]*STORAGE[[:space:]]*\\)[[:space:]]*#+[[:space:]]*$" docker-compose.yml; then
# Add to STORAGE section (original behavior)
awk -v insert_key="$key" -v insert_val="$value" '
BEGIN{printed=0}
/^[[:space:]]*#+[[:space:]]*OPTIONAL[[:space:]]*\([[:space:]]*STORAGE[[:space:]]*\)[[:space:]]*#+[[:space:]]*$/ {print; in_storage=1; next}
in_storage && /^[[:space:]]*#+.*OPTIONAL.*OAUTH/ && !printed {
print " " insert_key ": \"" insert_val "\""; printed=1; print; in_storage=0; next
}
{ print }
END { if(in_storage && !printed) print " " insert_key ": \"" insert_val "\"" }
' docker-compose.yml > tmp.yml && mv tmp.yml docker-compose.yml
section_found=true
fi
# Fallback: add at the end of environment block if section not found
if [[ "$section_found" == false ]]; then
awk -v insert_key="$key" -v insert_val="$value" '
/^ environment:/ {print; in_env=1; next}
in_env && /^[^[:space:]]/ {
if (!printed) { print " " insert_key ": \"" insert_val "\""; printed=1 }
in_env=0
}
{ print }
END { if(in_env && !printed) print " " insert_key ": \"" insert_val "\"" }
' docker-compose.yml > tmp.yml && mv tmp.yml docker-compose.yml
fi
fi
}
# Function to check if we're in the correct directory
check_formbricks_directory() {
if [[ ! -f "docker-compose.yml" ]]; then
print_error "docker-compose.yml not found in current directory!"
print_info "Please run this script from your Formbricks installation directory (usually ./formbricks/)"
exit 1
# Case 1: docker-compose.yml in current directory
if [[ -f "docker-compose.yml" ]]; then
if grep -q "formbricks" docker-compose.yml; then
return 0
else
print_error "This doesn't appear to be a Formbricks docker-compose.yml file!"
exit 1
fi
fi
if ! grep -q "formbricks" docker-compose.yml; then
print_error "This doesn't appear to be a Formbricks docker-compose.yml file!"
exit 1
# Case 2: one-click setup parent directory containing ./formbricks/docker-compose.yml
if [[ -f "formbricks/docker-compose.yml" ]]; then
cd formbricks
print_status "Detected one-click setup layout. Switched to ./formbricks directory."
if ! grep -q "formbricks" docker-compose.yml; then
print_error "This doesn't appear to be a Formbricks docker-compose.yml file!"
exit 1
fi
return 0
fi
# Neither current directory nor ./formbricks contains a compose file
print_error "docker-compose.yml not found in current directory or in ./formbricks/"
print_info "Run this script from the parent directory created by the one-click setup (containing ./formbricks/), or from the directory containing docker-compose.yml."
exit 1
}
# Function to backup existing docker-compose.yml
@@ -635,35 +684,202 @@ ${tls_block}
print_status "Added MinIO service to docker-compose.yml"
}
# Function to add minio-init dependency to formbricks service
add_minio_dependency() {
# Only add if not already present
if ! awk '/formbricks:/,/depends_on:/{ if($0 ~ /minio-init/) found=1 } END{ exit(found) }' docker-compose.yml; then
if sed --version >/dev/null 2>&1; then sed -i '/formbricks:/,/depends_on:/{/- postgres/a\
- minio-init
}' docker-compose.yml; else sed -i '' '/formbricks:/,/depends_on:/{/- postgres/a\
- minio-init
}' docker-compose.yml; fi
print_status "Added minio-init dependency to formbricks service"
# Generic function to add service dependency
add_service_dependency() {
local service="$1" # Target service (e.g., "formbricks", "traefik")
local dependency="$2" # Dependency to add (e.g., "redis", "minio-init", "minio")
local optional="${3:-false}" # Optional parameter - if true, don't exit if service not found
# Check if service exists
if ! grep -q "^ $service:" docker-compose.yml; then
if [[ "$optional" == "true" ]]; then
print_info "$service service not found - skipping dependency addition."
return 0
else
print_error "$service service not found in docker-compose.yml!"
print_info "Please ensure the $service service is properly configured before running this migration."
exit 1
fi
fi
# Check if dependency is already present in the service depends_on section
if awk -v srv="$service" -v dep="$dependency" 'BEGIN{found=0} /^ / && $0 ~ "^ " srv ":" {in_svc=1} in_svc && /^ [a-zA-Z]/ && $0 !~ "^ " srv ":" {in_svc=0} in_svc && $0 ~ "^[[:space:]]*-[[:space:]]*" dep "[[:space:]]*$" {found=1} END{exit(!found)}' docker-compose.yml; then
# Dependency already present, skip addition
print_info "$dependency dependency already present in $service service."
else
print_info "minio-init dependency already present."
# Write awk script to temporary file to avoid shell escaping issues
local awk_script_tmp
awk_script_tmp=$(mktemp)
cat > "$awk_script_tmp" << 'AWK_EOF'
# Store all lines to be able to look ahead
{
lines[NR] = $0
}
END {
# First pass: check if target service has depends_on
for (i = 1; i <= NR; i++) {
if (lines[i] ~ "^ " srv ":") {
in_target = 1
start_line = i
} else if (in_target && lines[i] ~ /^ [a-zA-Z]/ && lines[i] !~ "^ " srv ":") {
in_target = 0
end_line = i - 1
break
}
if (in_target && lines[i] ~ /^[[:space:]]*depends_on:[[:space:]]*$/) {
has_depends_on = 1
}
}
if (in_target && !end_line) end_line = NR
# Second pass: output with modifications
in_svc = 0; in_depends = 0; added = 0
for (i = 1; i <= NR; i++) {
line = lines[i]
if (line ~ "^ " srv ":") {
in_svc = 1
print line
continue
}
if (in_svc && line ~ /^ [a-zA-Z]/ && line !~ "^ " srv ":") {
# Exiting service
if (!has_depends_on && !added) {
print " depends_on:"
print " - " new_dep
added = 1
}
in_svc = 0; in_depends = 0
print line
continue
}
if (in_svc && line ~ /^[[:space:]]*depends_on:[[:space:]]*$/) {
in_depends = 1
print line
continue
}
if (in_svc && in_depends && line ~ /^[[:space:]]*-[[:space:]]*/) {
print line
continue
}
if (in_svc && in_depends && line !~ /^[[:space:]]*-[[:space:]]*/) {
# End of depends_on list
if (!added) {
print " - " new_dep
added = 1
}
in_depends = 0
print line
continue
}
if (in_svc && line ~ /^[[:space:]]+[a-zA-Z_][^:]*:[[:space:]]*/) {
# First property in service without depends_on
if (!has_depends_on && !added) {
print " depends_on:"
print " - " new_dep
added = 1
has_depends_on = 1
}
print line
continue
}
print line
}
# Handle case where service ends at EOF
if (in_depends && !added) {
print " - " new_dep
} else if (in_svc && !has_depends_on && !added) {
print " depends_on:"
print " - " new_dep
}
}
AWK_EOF
# Use the awk script with variables
awk -v srv="$service" -v new_dep="$dependency" -f "$awk_script_tmp" docker-compose.yml > tmp.yml && mv tmp.yml docker-compose.yml
rm -f "$awk_script_tmp"
print_status "Added $dependency dependency to $service service"
fi
}
# Function to update Traefik configuration to include MinIO dependency
update_traefik_config() {
# Check if traefik service exists and add minio dependency
if grep -q "traefik:" docker-compose.yml; then
if ! awk '/traefik:/,/depends_on:/{ if($0 ~ /- minio$/) found=1 } END{ exit(found) }' docker-compose.yml; then
if sed --version >/dev/null 2>&1; then sed -i '/traefik:/,/depends_on:/{/- formbricks/a\
- minio
}' docker-compose.yml; else sed -i '' '/traefik:/,/depends_on:/{/- formbricks/a\
- minio
}' docker-compose.yml; fi
print_status "Updated Traefik configuration to include MinIO dependency"
else
print_info "Traefik already depends_on minio."
fi
# Function to add Redis environment variable
add_redis_environment_variables() {
# Use the enhanced helper function with REQUIRED section
add_or_replace_env_var "REDIS_URL" "redis://redis:6379" "REQUIRED"
print_status "Redis environment variables ensured in docker-compose.yml"
}
# Function to add Redis service to docker-compose.yml
add_redis_service() {
# Skip injecting if service already exists
if has_service redis; then
print_info "Redis service already present. Skipping service injection."
return 0
fi
# Create Redis service configuration
local redis_service_config="
redis:
restart: always
image: valkey/valkey@sha256:12ba4f45a7c3e1d0f076acd616cb230834e75a77e8516dde382720af32832d6d
command: valkey-server --appendonly yes
volumes:
- redis:/data
"
# Write Redis service to temporary file
echo "$redis_service_config" > redis_service.tmp
# Add Redis service before the volumes section
awk '
{
print
if ($0 ~ /^services:$/ && !inserted) {
while ((getline line < "redis_service.tmp") > 0) print line
close("redis_service.tmp")
inserted = 1
}
}
' docker-compose.yml > tmp.yml && mv tmp.yml docker-compose.yml
# Clean up temporary file
rm -f redis_service.tmp
print_status "Added Redis service to docker-compose.yml"
}
# Function to add redis volume
add_redis_volume() {
# Ensure redis volume exists once
if grep -q '^volumes:' docker-compose.yml; then
# volumes block exists; check for redis inside it
if awk '/^volumes:/{invol=1; next} invol && NF==0{invol=0} invol{ if($1=="redis:") found=1 } END{ exit(!found) }' docker-compose.yml; then
print_info "Redis volume already present."
else
awk '
/^volumes:/ { print; invol=1; next }
invol && /^[^[:space:]]/ { if(!added){ print " redis:"; print " driver: local"; added=1 } ; invol=0 }
{ print }
END { if (invol && !added) { print " redis:"; print " driver: local" } }
' docker-compose.yml > tmp.yml && mv tmp.yml docker-compose.yml
print_status "Redis volume ensured"
fi
else
# no volumes block; append one with redis only (non-destructive to services)
{
echo ""
echo "volumes:"
echo " redis:"
echo " driver: local"
} >> docker-compose.yml
print_status "Added volumes section with Redis"
fi
}
@@ -885,19 +1101,21 @@ migrate_files_to_minio() {
# Function to restart Docker Compose
restart_docker_compose() {
echo -n "Restart Docker Compose now to start MinIO and apply changes? [Y/n]: "
echo -n "Restart now to apply changes and continue the migration? (recommended) [Y/n]: "
local restart_confirm
read -r restart_confirm
restart_confirm=$(echo "$restart_confirm" | tr '[:upper:]' '[:lower:]')
if [[ -z "$restart_confirm" || "$restart_confirm" == "y" ]]; then
print_info "Stopping current services..."
print_info "We need to briefly restart Formbricks to apply the changes."
print_info "Stopping services..."
docker compose down
print_info "Starting services with MinIO..."
docker compose up -d
print_status "Docker Compose restarted successfully!"
return 0
else
print_warning "Skipping restart. You can run 'docker compose down && docker compose up -d' later to start MinIO."
print_warning "Skipping restart for now."
print_info "When you're ready, run: docker compose down && docker compose up -d"
return 1
fi
}
@@ -924,16 +1142,34 @@ wait_for_service_up() {
}
# Main migration function
migrate_to_minio() {
echo "🧱 Formbricks MinIO Migration Script for v4.0"
echo "=============================================="
migrate_to_v4() {
echo "🧱 Formbricks v4.0 Migration"
echo "============================"
echo ""
print_info "We'll prepare your Formbricks instance for v4.0 by:"
print_info "- Adding Redis (for caching)"
print_info "- Adding MinIO (for file storage)"
print_info "- Moving your existing files into MinIO"
print_info "You'll be asked to restart briefly so changes take effect."
echo ""
# Check if we're in the right directory
check_formbricks_directory
# Backup docker-compose.yml before making any changes
backup_docker_compose
# Add Redis configuration first (prerequisite for Formbricks 4.0)
print_status "Setting up Redis..."
add_redis_environment_variables
add_redis_service
add_redis_volume
add_service_dependency "formbricks" "redis"
echo ""
# Abort early if external S3 already configured and no bundled MinIO
external_s3_guard
# Check if MinIO is already configured unless migrating only
if [[ "$MIGRATE_ONLY" != true ]]; then
if [[ "$FORCE_RECONFIGURE" == true ]]; then
@@ -954,8 +1190,8 @@ migrate_to_minio() {
fi
print_info "Detected configuration:"
print_info " Main domain: $main_domain"
print_info " HTTPS setup: $https_setup"
print_info "Main domain: $main_domain"
print_info "HTTPS setup: $https_setup"
echo ""
local files_domain
@@ -984,7 +1220,7 @@ migrate_to_minio() {
exit 1
fi
local default_files_domain="files.$main_domain"
echo -n "Enter the files subdomain for MinIO (e.g., $default_files_domain): "
echo -n "Enter the files subdomain to use for MinIO (e.g., $default_files_domain): "
read files_domain
if [[ -z "$files_domain" ]]; then
files_domain="$default_files_domain"
@@ -998,9 +1234,6 @@ migrate_to_minio() {
generate_minio_credentials
echo ""
# Backup docker-compose.yml
backup_docker_compose
# If reconfiguring/rotating creds, remove existing service blocks so reinjection is clean
if [[ "$REGENERATE_CREDS" == true || "$FORCE_RECONFIGURE" == true ]]; then
remove_minio_services || true
@@ -1013,10 +1246,10 @@ migrate_to_minio() {
add_minio_service "$files_domain" "$main_domain" "$https_setup"
# Add MinIO dependency to formbricks
add_minio_dependency
add_service_dependency "formbricks" "minio-init"
# Update Traefik configuration
update_traefik_config
# Update Traefik configuration (optional - skip if traefik not present)
add_service_dependency "traefik" "minio" "true"
# Add MinIO volume
add_minio_volume
@@ -1034,6 +1267,26 @@ migrate_to_minio() {
if restart_docker_compose; then
restart_success=true
proceed_migration=true
else
# User declined restart; confirm they understand migration cannot proceed without it
print_warning "Without restarting now, the migration cannot proceed."
echo -n "Restart Docker Compose now to proceed with migration? [Y/n]: "
read -r confirm_restart
confirm_restart=$(echo "$confirm_restart" | tr '[:upper:]' '[:lower:]')
if [[ -z "$confirm_restart" || "$confirm_restart" == "y" ]]; then
if restart_docker_compose; then
restart_success=true
proceed_migration=true
else
print_warning "Migration cancelled because restart was declined."
print_info "You can run 'docker compose down && docker compose up -d' later, then rerun this script to migrate files."
return 0
fi
else
print_warning "Migration cancelled at your request. No files were migrated."
print_info "You can restart later and rerun this script to migrate files."
return 0
fi
fi
else
# Try to ensure services are up without full restart
@@ -1138,22 +1391,25 @@ migrate_to_minio() {
fi
echo ""
echo "🎉 MinIO Migration Complete!"
echo "============================="
echo "🎉 Formbricks v4.0 Migration Complete!"
echo "======================================="
echo ""
print_status "MinIO Configuration:"
print_info " Files Domain: $files_domain"
print_info " S3 Access Key: $minio_service_user"
print_info " S3 Bucket: $minio_bucket_name"
print_status "Infrastructure Configuration:"
print_info "Redis://redis:6379"
print_info "Files Domain: $files_domain"
print_info "S3 Access Key: $minio_service_user"
print_info "S3 Bucket: $minio_bucket_name"
echo ""
if [[ "$restart_success" == true ]]; then
print_status "Your Formbricks instance is now using MinIO for file storage!"
print_info "You can check the status with: docker compose ps"
print_info "View logs with: docker compose logs"
print_status "Your Formbricks instance is now ready for v4.0!"
print_info "- Redis is configured for caching"
print_info "- MinIO is configured for file storage"
print_info "To check that everything is running, use: docker compose ps"
print_info "To view logs (for troubleshooting), use: docker compose logs"
else
print_warning "Remember to restart your Docker Compose setup:"
print_info " docker compose down && docker compose up -d"
print_warning "Before migration can happen, please remember to restart your services:"
print_info "docker compose down && docker compose up -d"
fi
if [[ "$MIGRATE_ONLY" != true ]]; then
@@ -1211,6 +1467,6 @@ cleanup_uploads_from_compose() {
fi
# Check if script is being run directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
migrate_to_minio
if [[ -n "${BASH_SOURCE:-}" && "${BASH_SOURCE[0]}" == "${0}" ]]; then
migrate_to_v4
fi

View File

@@ -261,7 +261,6 @@
"group": "Advanced",
"pages": [
"self-hosting/advanced/migration",
"self-hosting/setup/minio-migration",
"self-hosting/advanced/license",
"self-hosting/advanced/license-activation",
{

View File

@@ -4,10 +4,148 @@ description: "Formbricks Self-hosted version migration"
icon: "arrow-right"
---
### v4.0
## v4.0
- Migrate local uploads to the new MinIO storage (required in 4.0):
see [minio migration](/self-hosting/setup/minio-migration)
<Warning>
**Important: Migration Required**
Formbricks 4 introduces additional requirements for self-hosting setups and makes a dedicated Redis cache as well as S3-compatible file storage mandatory.
</Warning>
Formbricks 4.0 is a **major milestone** that sets up the technical foundation for future iterations and feature improvements. This release focuses on modernizing core infrastructure components to improve reliability, scalability, and enable advanced features going forward.
### What's New in Formbricks 4.0
**🚀 New Enterprise Features:**
- **Quotas Management**: Advanced quota controls for enterprise users
**🏗️ Technical Foundation Improvements:**
- **Enhanced File Storage**: Improved file handling with better performance and reliability
- **Improved Caching**: New caching functionality improving speed, extensibility and reliability
- **Database Optimization**: Removal of unused database tables and fields for better performance
- **Future-Ready Architecture**: Standardized infrastructure components for upcoming features
### What This Means for Your Self-Hosting Setup
These improvements in Formbricks 4.0 also make some infrastructure requirements mandatory going forward:
- **Redis** for caching
- **MinIO or S3-compatible storage** for file uploads
These services are already included in the updated one-click setup for self-hosters, but existing users need to upgrade their setup. More information on this below.
### Why We Made These Changes
We know this represents more moving parts in your infrastructure and might even introduce more complexity in hosting Formbricks, and we don't take this decision lightly. As Formbricks grows into a comprehensive Survey and Experience Management platform, we've reached a point where the simple, single-service approach was holding back our ability to deliver the reliable, feature-rich product our users demand and deserve.
By moving to dedicated, professional-grade services for these critical functions, we're building the foundation needed to deliver:
- **Enterprise-grade reliability** with proper redundancy and backup capabilities
- **Advanced features** that require sophisticated caching and file processing
- **Better performance** through optimized, dedicated services
- **Future scalability** to support larger deployments and more complex use cases without the need to maintain two different approaches
We believe this is the only path forward to build the comprehensive Survey and Experience Management software we're aiming for.
### Migration Steps for v4.0
Additional migration steps are needed if you are using a self-hosted Formbricks setup that uses either local file storage (not S3-compatible file storage) or doesn't already use a Redis cache.
### One-Click Setup
For users using our official one-click setup, we provide an automated migration using a migration script:
```bash
# Download the latest script
curl -fsSL -o migrate-to-v4.sh \
https://raw.githubusercontent.com/formbricks/formbricks/stable/docker/migrate-to-v4.sh
# Make it executable
chmod +x migrate-to-v4.sh
# Launch the guided migration
./migrate-to-v4.sh
```
This script guides you through the steps for the infrastructure migration and does the following:
- Adds a Redis service to your setup and configures it
- Adds a MinIO service (open source S3-alternative) to your setup, configures it and migrates local files to it
- Pulls the latest Formbricks image and updates your instance
### Manual Setup
If you use a different setup to host your Formbricks instance, you need to make sure to make the necessary adjustments to run Formbricks 4.0.
#### Redis
Formbricks 4.0 requires a Redis instance to work properly. Please add a Redis instance to your Docker setup, your K8s infrastructure, or however you are hosting Formbricks at the moment. Formbricks works with the latest versions of Redis as well as Valkey.
You need to configure the `REDIS_URL` environment variable and point it to your Redis instance.
#### S3-compatible storage
To use file storage (e.g., file upload questions, image choice questions, custom survey backgrounds, etc.), you need to have S3-compatible file storage set up and connected to Formbricks.
Formbricks supports multiple storage providers (among many other S3-compatible storages):
- AWS S3
- Digital Ocean Spaces
- Hetzner Object Storage
- Custom MinIO server
Please make sure to set up a storage bucket with one of these solutions and then link it to Formbricks using the following environment variables:
```
S3_ACCESS_KEY: your-access-key
S3_SECRET_KEY: your-secret-key
S3_REGION: us-east-1
S3_BUCKET_NAME: formbricks-uploads
S3_ENDPOINT_URL: http://minio:9000 # not needed for AWS S3
```
#### Upgrade Process
**1. Backup your Database**
**Critical Step**: Create a complete database backup before proceeding. Formbricks 4.0 will automatically remove unused database tables and fields during startup.
```bash
docker exec formbricks-postgres-1 pg_dump -Fc -U postgres -d formbricks > formbricks_pre_v4.0_$(date +%Y%m%d_%H%M%S).dump
```
<Info>
If you run into "**No such container**", use `docker ps` to find your container name,
e.g. `formbricks_postgres_1`.
</Info>
**2. Upgrade to Formbricks 4.0**
Pull the latest Docker images and restart the setup (example for docker-compose):
```bash
# Pull the latest version
docker compose pull
# Stop the current instance
docker compose down
# Start with Formbricks 4.0
docker compose up -d
```
**3. Automatic Database Migration**
When you start Formbricks 4.0 for the first time, it will **automatically**:
- Detect and apply required database schema updates
- Remove unused database tables and fields
- Optimize the database structure for better performance
No manual intervention is required for the database migration.
**4. Verify Your Upgrade**
- Access your Formbricks instance at the same URL as before
- Test file uploads to ensure S3/MinIO integration works correctly
- Verify that existing surveys and data are intact
- Check that previously uploaded files are accessible
### v3.3

View File

@@ -1,106 +0,0 @@
---
title: Migrate local uploads to MinIO
description: Oneclick migration from local file storage to the new MinIO storage required in 4.0
icon: "cloud"
---
## Why this migration is required
Formbricks 4.0 uses an S3compatible object store for all file uploads. For OneClick installs that previously stored files locally (named volume `uploads` or a host bind like `./uploads`), you must migrate those files to MinIO.
This guide shows a safe, idempotent migration using the script `docker/migrate-to-minio.sh` included in this repository.
<Note>
This migration script is supported on Ubuntu only (Ubuntu 20.04+), requiring Bash 4+ and GNU sed/grep.
</Note>
## Prerequisites
- DNS: a subdomain for object storage, e.g. `files.<your-domain>` pointing to your server IP
- Your OneClick installation directory (contains `docker-compose.yml`)
- Docker/Compose installed and working
## What the script does
- Adds MinIO services (`minio`, `minio-init`) and S3 env vars to `docker-compose.yml`
- Asks to restart your Compose stack (you should say Yes)
- Detects your existing upload sources poststart and migrates them to MinIO
- Host bind mounts (e.g., `./uploads`)
- Named volume target in the container (legacy: `/home/nextjs/apps/web/uploads` or custom target)
- Absolute `UPLOADS_DIR` in the container if set
- Cleans up `docker-compose.yml` after success: removes `UPLOADS_DIR`, removes all `uploads` volume mappings and the root `uploads:` definition
- Optionally restarts again to apply the cleanup
The migration is idempotent: you can rerun it; existing objects are not duplicated.
## Run the migration
From the directory that contains your `docker-compose.yml`:
```bash
# Download the latest script
curl -fsSL -o migrate-to-minio.sh \
https://raw.githubusercontent.com/formbricks/formbricks/stable/docker/migrate-to-minio.sh
# Make it executable
chmod +x migrate-to-minio.sh
# Launch the guided migration
./migrate-to-minio.sh
```
## Verifying the migration
- Check the UI: previously uploaded images/logos/files should load
- List bucket contents with `mc` (reuses minioinit service env):
```bash
docker compose run --rm --entrypoint /bin/sh minio-init -lc \
'mc alias set minio http://minio:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" && \
mc ls -r "minio/$MINIO_BUCKET_NAME" | head -200'
```
- Confirm `docker-compose.yml` no longer contains `UPLOADS_DIR` or `uploads:` volume mappings (postcleanup)
## Notes on sources
- The script migrates from multiple sources, in order:
- Host bind (absolute path resolved by `docker compose config`)
- Container mount target for named volume `uploads` (e.g., `/home/nextjs/apps/web/uploads` or your custom `.../files/uploads`)
- Absolute `UPLOADS_DIR` (container path)
- The relative path/key structure is preserved: `${environmentId}/${accessType}/fileName.ext`
- Reruns copy only new/changed files (idempotent)
## Troubleshooting
- MinIO 404 on file URLs
- If you use virtualhosted style (`bucket.files.domain`), ensure Traefik router rule includes wildcard:
```
traefik.http.routers.minio-s3.rule=Host(`files.domain`) || HostRegexp(`{any:.+}.files.domain`)
```
- InvalidAccessKeyId in UI
- Happens if keys rotated between runs. Rerun the script; it reuses S3\_\* credentials and reattaches policy.
- Undefined volume `minio-data`
- Rerun; the script ensures `minio-data` exists in the root `volumes:` block.
- List bucket contents
```bash
docker compose run --rm --entrypoint /bin/sh minio-init -lc \
'mc alias set minio http://minio:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD"; \
mc ls -r "minio/$MINIO_BUCKET_NAME" | head -200'
```
## Rollback
- Your original `docker-compose.yml` is backed up as `docker-compose.yml.backup.<timestamp>`
- To revert, restore the backup and restart Compose:
```bash
cp docker-compose.yml.backup.<timestamp> docker-compose.yml
docker compose down && docker compose up -d
```
Thats it — youre on the new MinIO storage for Formbricks 4.0. ✅