diff --git a/docker/migrate-to-minio.sh b/docker/migrate-to-v4.sh similarity index 76% rename from docker/migrate-to-minio.sh rename to docker/migrate-to-v4.sh index 71be9098c7..5c6161c578 100755 --- a/docker/migrate-to-minio.sh +++ b/docker/migrate-to-v4.sh @@ -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 diff --git a/docs/docs.json b/docs/docs.json index b6cb68362a..85b2c11056 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -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", { diff --git a/docs/self-hosting/advanced/migration.mdx b/docs/self-hosting/advanced/migration.mdx index dbd1de4e7a..6e2190ae7b 100644 --- a/docs/self-hosting/advanced/migration.mdx +++ b/docs/self-hosting/advanced/migration.mdx @@ -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) + + **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. + + +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 +``` + + + If you run into "**No such container**", use `docker ps` to find your container name, + e.g. `formbricks_postgres_1`. + + +**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 diff --git a/docs/self-hosting/setup/minio-migration.mdx b/docs/self-hosting/setup/minio-migration.mdx deleted file mode 100644 index a70ecf6ada..0000000000 --- a/docs/self-hosting/setup/minio-migration.mdx +++ /dev/null @@ -1,106 +0,0 @@ ---- -title: Migrate local uploads to MinIO -description: One‑click 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 S3‑compatible object store for all file uploads. For One‑Click 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. - - - This migration script is supported on Ubuntu only (Ubuntu 20.04+), requiring Bash 4+ and GNU sed/grep. - - -## Prerequisites - -- DNS: a subdomain for object storage, e.g. `files.` pointing to your server IP -- Your One‑Click 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 post‑start 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 re‑run 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 minio‑init 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 (post‑cleanup) - -## 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` -- Re‑runs copy only new/changed files (idempotent) - -## Troubleshooting - -- MinIO 404 on file URLs - - If you use virtual‑hosted 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. Re‑run the script; it reuses S3\_\* credentials and reattaches policy. - -- Undefined volume `minio-data` - - - Re‑run; 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.` -- To revert, restore the backup and restart Compose: - -```bash -cp docker-compose.yml.backup. docker-compose.yml -docker compose down && docker compose up -d -``` - -That’s it — you’re on the new MinIO storage for Formbricks 4.0. ✅