🚨 Before executing any scripts, make sure to create a full database dump first!
Run a backup of the Coolify PostgreSQL database before proceeding with any migration or modification scripts. In case of an error, this backup is your only way to restore the previous state.
🚨 Before executing any scripts, make sure to create a full database dump first!
Run a backup of the Coolify PostgreSQL database before proceeding with any migration or modification scripts. In case of an error, this backup is your only way to restore the previous state.
🚨 Before executing any scripts, make sure to create a full database dump first!
Run a backup of the Coolify PostgreSQL database before proceeding with any migration or modification scripts. In case of an error, this backup is your only way to restore the previous state.
Kacper Zawojski
Software Engineer (Fullstack)
Kacper Zawojski is a full-stack developer and PayloadCMS expert with a background in algorithmic computer science from Wrocław University of Technology and a master's in Mechatronics and Robotics from Warsaw University of Technology. He's been building production web products at WAYF since early 2024, working across React, TypeScript, Next.js, and Payload — the stack that powers most of our client work.
0. Pre-flight: Database backup
Before running any migration scripts, create a full dump of the Coolify database. The SSH tunnel from section 1 must be running on port 5433.
0.1 Create a dump
Run from your local machine (requires the SSH tunnel from section 1.3 to be active):
💡If you see an error about a missing ROLE (e.g. master), create it first in your local database:
CREATE ROLE master;
This document describes how to connect to the Coolify PostgreSQL database, the data model, and procedures for migrating projects and servers between teams (either an entire team or a single project). It includes SQL templates and examples.
1. Connecting to the Coolify database (production)
The Coolify PostgreSQL database runs in a Docker container on the server and is not exposed to the internet. Access is via an SSH tunnel.
1.1 Requirements
SSH key with access to the server (e.g. ~/.ssh/id_rsa or your preferred key)
Moving a project is done by updating projects.team_id and any related resources that have their own team_id (servers, keys, backups, etc.).
3. Tables with a direct team_id
Table
Notes
projects
Main table — moving it moves environments and everything under them
servers
Servers hosting resources
private_keys
SSH keys
github_apps
GitHub integrations
gitlab_apps
GitLab integrations
s3_storages
Backup storage
tags
Resource tags
scheduled_database_backups
Scheduled backups
scheduled_tasks
Cron tasks
shared_environment_variables
Shared env variables
discord_notification_settings
Discord notifications
email_notification_settings
Email notifications
slack_notification_settings
Slack notifications
telegram_notification_settings
Telegram notifications
pushover_notification_settings
Pushover notifications
subscriptions
Stripe subscriptions
team_user
Team ↔ user pivot (optional)
personal_access_tokens
API tokens (team_id is varchar)
4. Migration scenarios
4.1 Migrating an entire team A → B
All team resources are moved (projects, servers, keys, integrations, backups, notifications, etc.). Use the template in 5.1.
4.2 Migrating only resources tied to a single project
Only the following are moved:
The chosen project (projects.id = X);
Servers that are the destination for applications, services, or standalone DBs in that project’s environments (with optional exclusions, e.g. localhost);
Private keys, GitHub/GitLab apps, S3, tags, scheduled backups/tasks, shared env vars — only those linked to that project.
Notification settings, subscriptions, and personal_access_tokens are not moved (they stay at team level).
4.3 Reverting a single server to its previous team
E.g. the “localhost” server (id=0) should remain in team 0 despite the project migration. Template: 6.1.
5. Scripts and templates
5.1 Template: Migrate entire team A → B
Note: Replace old_team and new_team with the correct IDs. Back up the database before running and run the state check query (5.4) first.
BEGIN;
DO $$
DECLARE
old_team bigint := 0; -- <-- CHANGE: source team ID
new_team bigint := 1; -- <-- CHANGE: target team ID
env_ids bigint[];
proj_ids bigint[];
BEGINSELECT array_agg(id)INTO proj_ids FROM projects WHERE team_id = old_team;
UPDATE projects SET team_id = new_team WHERE team_id = old_team;
UPDATE servers SET team_id = new_team WHERE team_id = old_team;
SELECT array_agg(id)INTO env_ids
FROM environments WHERE project_id = ANY(proj_ids);
UPDATE private_keys SET team_id = new_team WHERE team_id = old_team;
UPDATE github_apps SET team_id = new_team WHERE team_id = old_team;
UPDATE gitlab_apps SET team_id = new_team WHERE team_id = old_team;
UPDATE s3_storages SET team_id = new_team WHERE team_id = old_team;
UPDATE tags SET team_id = new_team WHERE team_id = old_team;
UPDATE scheduled_database_backups SET team_id = new_team WHERE team_id = old_team;
UPDATE scheduled_tasks SET team_id = new_team WHERE team_id = old_team;
UPDATE shared_environment_variables SET team_id = new_team WHERE team_id = old_team;
UPDATE discord_notification_settings SET team_id = new_team WHERE team_id = old_team;
UPDATE email_notification_settings SET team_id = new_team WHERE team_id = old_team;
UPDATE slack_notification_settings SET team_id = new_team WHERE team_id = old_team;
UPDATE telegram_notification_settings SET team_id = new_team WHERE team_id = old_team;
UPDATE pushover_notification_settings SET team_id = new_team WHERE team_id = old_team;
UPDATE subscriptions SET team_id = new_team WHERE team_id = old_team;
UPDATE personal_access_tokens SET team_id = new_team::text WHERE team_id = old_team::text;
-- Optional: move team members-- UPDATE team_user SET team_id = new_team WHERE team_id = old_team;
RAISE NOTICE 'Moved resources from team % to team %', old_team, new_team;
END $$;
-- Verify before COMMIT:-- SELECT id, name, team_id FROM projects;-- SELECT id, name, team_id FROM servers;COMMIT;
-- On failure: ROLLBACK;
BEGIN;
DO $$
DECLARE
old_team bigint := 0; -- <-- CHANGE: source team ID
new_team bigint := 1; -- <-- CHANGE: target team ID
env_ids bigint[];
proj_ids bigint[];
BEGINSELECT array_agg(id)INTO proj_ids FROM projects WHERE team_id = old_team;
UPDATE projects SET team_id = new_team WHERE team_id = old_team;
UPDATE servers SET team_id = new_team WHERE team_id = old_team;
SELECT array_agg(id)INTO env_ids
FROM environments WHERE project_id = ANY(proj_ids);
UPDATE private_keys SET team_id = new_team WHERE team_id = old_team;
UPDATE github_apps SET team_id = new_team WHERE team_id = old_team;
UPDATE gitlab_apps SET team_id = new_team WHERE team_id = old_team;
UPDATE s3_storages SET team_id = new_team WHERE team_id = old_team;
UPDATE tags SET team_id = new_team WHERE team_id = old_team;
UPDATE scheduled_database_backups SET team_id = new_team WHERE team_id = old_team;
UPDATE scheduled_tasks SET team_id = new_team WHERE team_id = old_team;
UPDATE shared_environment_variables SET team_id = new_team WHERE team_id = old_team;
UPDATE discord_notification_settings SET team_id = new_team WHERE team_id = old_team;
UPDATE email_notification_settings SET team_id = new_team WHERE team_id = old_team;
UPDATE slack_notification_settings SET team_id = new_team WHERE team_id = old_team;
UPDATE telegram_notification_settings SET team_id = new_team WHERE team_id = old_team;
UPDATE pushover_notification_settings SET team_id = new_team WHERE team_id = old_team;
UPDATE subscriptions SET team_id = new_team WHERE team_id = old_team;
UPDATE personal_access_tokens SET team_id = new_team::text WHERE team_id = old_team::text;
-- Optional: move team members-- UPDATE team_user SET team_id = new_team WHERE team_id = old_team;
RAISE NOTICE 'Moved resources from team % to team %', old_team, new_team;
END $$;
-- Verify before COMMIT:-- SELECT id, name, team_id FROM projects;-- SELECT id, name, team_id FROM servers;COMMIT;
-- On failure: ROLLBACK;
BEGIN;
DO $$
DECLARE
old_team bigint := 0; -- <-- CHANGE: source team ID
new_team bigint := 1; -- <-- CHANGE: target team ID
env_ids bigint[];
proj_ids bigint[];
BEGINSELECT array_agg(id)INTO proj_ids FROM projects WHERE team_id = old_team;
UPDATE projects SET team_id = new_team WHERE team_id = old_team;
UPDATE servers SET team_id = new_team WHERE team_id = old_team;
SELECT array_agg(id)INTO env_ids
FROM environments WHERE project_id = ANY(proj_ids);
UPDATE private_keys SET team_id = new_team WHERE team_id = old_team;
UPDATE github_apps SET team_id = new_team WHERE team_id = old_team;
UPDATE gitlab_apps SET team_id = new_team WHERE team_id = old_team;
UPDATE s3_storages SET team_id = new_team WHERE team_id = old_team;
UPDATE tags SET team_id = new_team WHERE team_id = old_team;
UPDATE scheduled_database_backups SET team_id = new_team WHERE team_id = old_team;
UPDATE scheduled_tasks SET team_id = new_team WHERE team_id = old_team;
UPDATE shared_environment_variables SET team_id = new_team WHERE team_id = old_team;
UPDATE discord_notification_settings SET team_id = new_team WHERE team_id = old_team;
UPDATE email_notification_settings SET team_id = new_team WHERE team_id = old_team;
UPDATE slack_notification_settings SET team_id = new_team WHERE team_id = old_team;
UPDATE telegram_notification_settings SET team_id = new_team WHERE team_id = old_team;
UPDATE pushover_notification_settings SET team_id = new_team WHERE team_id = old_team;
UPDATE subscriptions SET team_id = new_team WHERE team_id = old_team;
UPDATE personal_access_tokens SET team_id = new_team::text WHERE team_id = old_team::text;
-- Optional: move team members-- UPDATE team_user SET team_id = new_team WHERE team_id = old_team;
RAISE NOTICE 'Moved resources from team % to team %', old_team, new_team;
END $$;
-- Verify before COMMIT:-- SELECT id, name, team_id FROM projects;-- SELECT id, name, team_id FROM servers;COMMIT;
-- On failure: ROLLBACK;
5.2 Template: Dry-run — migrate single project
Read-only queries — they show what would be moved for one project to the chosen team. Replace 10 (project_id) and 11 (new_team) with your values.
SELECT id, name, team_id FROM projects WHERE id = 10;
SELECT id, name FROM teams WHERE id = 11
SELECT id, name, team_id FROM projects WHERE id = 10;
SELECT id, name FROM teams WHERE id = 11
SELECT id, name, team_id FROM projects WHERE id = 10;
SELECT id, name FROM teams WHERE id = 11
Servers linked to the project (example for project_id = 10):
WITH envs AS(SELECT id FROM environments WHERE project_id = 10),
dest_servers AS(SELECT sd.server_id FROM applications a
JOIN envs e ON e.id = a.environment_id
JOIN standalone_dockers sd ON a.destination_type LIKE'%StandaloneDocker'AND a.destination_id = sd.id
UNIONSELECT sw.server_id FROM applications a JOIN envs e ON e.id = a.environment_id
JOIN swarm_dockers sw ON a.destination_type LIKE'%SwarmDocker'AND a.destination_id = sw.id
UNIONSELECT sd.server_id FROM services srv JOIN envs e ON e.id = srv.environment_id
JOIN standalone_dockers sd ON srv.destination_type LIKE'%StandaloneDocker'AND srv.destination_id = sd.id
UNIONSELECT sw.server_id FROM services srv JOIN envs e ON e.id = srv.environment_id
JOIN swarm_dockers sw ON srv.destination_type LIKE'%SwarmDocker'AND srv.destination_id = sw.id
UNIONSELECT sd.server_id FROM standalone_postgresqls sp JOIN envs e ON e.id = sp.environment_id
JOIN standalone_dockers sd ON sp.destination_type LIKE'%StandaloneDocker'AND sp.destination_id = sd.id
UNIONSELECT sw.server_id FROM standalone_postgresqls sp JOIN envs e ON e.id = sp.environment_id
JOIN swarm_dockers sw ON sp.destination_type LIKE'%SwarmDocker'AND sp.destination_id = sw.id
)SELECT s.id, s.name, s.team_id FROM dest_servers ds
JOIN servers s ON s.id = ds.server_id
ORDERBY
WITH envs AS(SELECT id FROM environments WHERE project_id = 10),
dest_servers AS(SELECT sd.server_id FROM applications a
JOIN envs e ON e.id = a.environment_id
JOIN standalone_dockers sd ON a.destination_type LIKE'%StandaloneDocker'AND a.destination_id = sd.id
UNIONSELECT sw.server_id FROM applications a JOIN envs e ON e.id = a.environment_id
JOIN swarm_dockers sw ON a.destination_type LIKE'%SwarmDocker'AND a.destination_id = sw.id
UNIONSELECT sd.server_id FROM services srv JOIN envs e ON e.id = srv.environment_id
JOIN standalone_dockers sd ON srv.destination_type LIKE'%StandaloneDocker'AND srv.destination_id = sd.id
UNIONSELECT sw.server_id FROM services srv JOIN envs e ON e.id = srv.environment_id
JOIN swarm_dockers sw ON srv.destination_type LIKE'%SwarmDocker'AND srv.destination_id = sw.id
UNIONSELECT sd.server_id FROM standalone_postgresqls sp JOIN envs e ON e.id = sp.environment_id
JOIN standalone_dockers sd ON sp.destination_type LIKE'%StandaloneDocker'AND sp.destination_id = sd.id
UNIONSELECT sw.server_id FROM standalone_postgresqls sp JOIN envs e ON e.id = sp.environment_id
JOIN swarm_dockers sw ON sp.destination_type LIKE'%SwarmDocker'AND sp.destination_id = sw.id
)SELECT s.id, s.name, s.team_id FROM dest_servers ds
JOIN servers s ON s.id = ds.server_id
ORDERBY
WITH envs AS(SELECT id FROM environments WHERE project_id = 10),
dest_servers AS(SELECT sd.server_id FROM applications a
JOIN envs e ON e.id = a.environment_id
JOIN standalone_dockers sd ON a.destination_type LIKE'%StandaloneDocker'AND a.destination_id = sd.id
UNIONSELECT sw.server_id FROM applications a JOIN envs e ON e.id = a.environment_id
JOIN swarm_dockers sw ON a.destination_type LIKE'%SwarmDocker'AND a.destination_id = sw.id
UNIONSELECT sd.server_id FROM services srv JOIN envs e ON e.id = srv.environment_id
JOIN standalone_dockers sd ON srv.destination_type LIKE'%StandaloneDocker'AND srv.destination_id = sd.id
UNIONSELECT sw.server_id FROM services srv JOIN envs e ON e.id = srv.environment_id
JOIN swarm_dockers sw ON srv.destination_type LIKE'%SwarmDocker'AND srv.destination_id = sw.id
UNIONSELECT sd.server_id FROM standalone_postgresqls sp JOIN envs e ON e.id = sp.environment_id
JOIN standalone_dockers sd ON sp.destination_type LIKE'%StandaloneDocker'AND sp.destination_id = sd.id
UNIONSELECT sw.server_id FROM standalone_postgresqls sp JOIN envs e ON e.id = sp.environment_id
JOIN swarm_dockers sw ON sp.destination_type LIKE'%SwarmDocker'AND sp.destination_id = sw.id
)SELECT s.id, s.name, s.team_id FROM dest_servers ds
JOIN servers s ON s.id = ds.server_id
ORDERBY
Use the dry-run to decide which servers to exclude (e.g. localhost) before running the actual migration.
5.3 Template: Migrate single project (actual migration)
Moves: the chosen project, only the related servers (with optional exclusion of e.g. server_id = 0), private keys, GitHub/GitLab apps, S3, tags, scheduled backups/tasks, and shared env vars tied to that project.
Parameters to set at the start of the block:
project_id_to_move — ID of the project to move
old_team — current team (e.g. 0)
new_team — target team (e.g. 11)
BEGIN;
DO $$
DECLARE
project_id_to_move bigint := 10; -- <-- CHANGE
old_team bigint := 0; -- <-- CHANGE
new_team bigint := 11; -- <-- CHANGE
env_ids bigint[];
server_ids bigint[];
key_ids bigint[];
github_ids bigint[];
gitlab_ids bigint[];
s3_ids bigint[];
tag_ids bigint[];
BEGINSELECT array_agg(id)INTO env_ids FROM environments WHERE project_id = project_id_to_move;
UPDATE projects SET team_id = new_team WHERE id = project_id_to_move AND team_id = old_team;
WITH dest_servers AS(SELECT sd.server_id FROM applications a
JOIN environments e ON e.id = a.environment_id AND e.project_id = project_id_to_move
JOIN standalone_dockers sd ON a.destination_type LIKE'%StandaloneDocker'AND a.destination_id = sd.id
UNIONSELECT sw.server_id FROM applications a
JOIN environments e ON e.id = a.environment_id AND e.project_id = project_id_to_move
JOIN swarm_dockers sw ON a.destination_type LIKE'%SwarmDocker'AND a.destination_id = sw.id
UNIONSELECT sd.server_id FROM services srv
JOIN environments e ON e.id = srv.environment_id AND e.project_id = project_id_to_move
JOIN standalone_dockers sd ON srv.destination_type LIKE'%StandaloneDocker'AND srv.destination_id = sd.id
UNIONSELECT sw.server_id FROM services srv
JOIN environments e ON e.id = srv.environment_id AND e.project_id = project_id_to_move
JOIN swarm_dockers sw ON srv.destination_type LIKE'%SwarmDocker'AND srv.destination_id = sw.id
UNIONSELECT sd.server_id FROM standalone_postgresqls sp
JOIN environments e ON e.id = sp.environment_id AND e.project_id = project_id_to_move
JOIN standalone_dockers sd ON sp.destination_type LIKE'%StandaloneDocker'AND sp.destination_id = sd.id
UNIONSELECT sw.server_id FROM standalone_postgresqls sp
JOIN environments e ON e.id = sp.environment_id AND e.project_id = project_id_to_move
JOIN swarm_dockers sw ON sp.destination_type LIKE'%SwarmDocker'AND sp.destination_id = sw.id
)SELECT array_agg(DISTINCT server_id)INTO server_ids FROM dest_servers;
-- Exclude localhost (id=0) or other servers — edit as neededIF server_ids ISNOTNULLAND array_length(server_ids,1) > 0THEN
server_ids := array_remove(server_ids,0::bigint);
ENDIF;
IF server_ids ISNOTNULLAND array_length(server_ids,1) > 0THENUPDATE servers SET team_id = new_team WHERE id = ANY(server_ids)AND team_id = old_team;
ENDIF;
SELECT array_agg(DISTINCT private_key_id)INTO key_ids
FROM applications WHERE environment_id = ANY(env_ids)AND private_key_id ISNOTNULL;
IF key_ids ISNOTNULLAND array_length(key_ids,1) > 0THENUPDATE private_keys SET team_id = new_team WHERE id = ANY(key_ids)AND team_id = old_team;
ENDIF;
SELECT array_agg(DISTINCT a.source_id)INTO github_ids
FROM applications a WHERE a.environment_id = ANY(env_ids)AND a.source_type ISNOTNULLAND a.source_type LIKE'%GitHub%';
IF github_ids ISNOTNULLAND array_length(github_ids,1) > 0THENUPDATE github_apps SET team_id = new_team WHERE id = ANY(github_ids)AND team_id = old_team;
ENDIF;
SELECT array_agg(DISTINCT a.source_id)INTO gitlab_ids
FROM applications a WHERE a.environment_id = ANY(env_ids)AND a.source_type ISNOTNULLAND a.source_type LIKE'%GitLab%';
IF gitlab_ids ISNOTNULLAND array_length(gitlab_ids,1) > 0THENUPDATE gitlab_apps SET team_id = new_team WHERE id = ANY(gitlab_ids)AND team_id = old_team;
ENDIF;
WITH backup_s3 AS(SELECT sdb.s3_storage_id FROM scheduled_database_backups sdb
JOIN standalone_postgresqls sp ON sdb.database_type LIKE'%StandalonePostgresql'AND sdb.database_id = sp.id AND sp.environment_id = ANY(env_ids)WHERE sdb.s3_storage_id ISNOTNULLUNIONSELECT sdb.s3_storage_id FROM scheduled_database_backups sdb
JOIN standalone_redis sr ON sdb.database_type LIKE'%StandaloneRedis'AND sdb.database_id = sr.id AND sr.environment_id = ANY(env_ids)WHERE sdb.s3_storage_id ISNOTNULLUNIONSELECT sdb.s3_storage_id FROM scheduled_database_backups sdb
JOIN standalone_mongodbs sm ON sdb.database_type LIKE'%StandaloneMongodb'AND sdb.database_id = sm.id AND sm.environment_id = ANY(env_ids)WHERE sdb.s3_storage_id ISNOTNULLUNIONSELECT sdb.s3_storage_id FROM scheduled_database_backups sdb
JOIN standalone_mysqls sm ON sdb.database_type LIKE'%StandaloneMysql'AND sdb.database_id = sm.id AND sm.environment_id = ANY(env_ids)WHERE sdb.s3_storage_id ISNOTNULLUNIONSELECT sdb.s3_storage_id FROM scheduled_database_backups sdb
JOIN standalone_mariadbs sm ON sdb.database_type LIKE'%StandaloneMariadb'AND sdb.database_id = sm.id AND sm.environment_id = ANY(env_ids)WHERE sdb.s3_storage_id ISNOTNULL)SELECT array_agg(DISTINCT s3_storage_id)INTO s3_ids FROM backup_s3 WHERE s3_storage_id ISNOTNULL;
IF s3_ids ISNOTNULLAND array_length(s3_ids,1) > 0THENUPDATE s3_storages SET team_id = new_team WHERE id = ANY(s3_ids)AND team_id = old_team;
ENDIF;
SELECT array_agg(DISTINCT t.tag_id)INTO tag_ids FROM taggables t
WHERE(t.taggable_type LIKE'%Application'AND t.taggable_id IN(SELECT id FROM applications WHERE environment_id = ANY(env_ids)))OR(t.taggable_type LIKE'%Service'AND t.taggable_id IN(SELECT id FROM services WHERE environment_id = ANY(env_ids)))OR(t.taggable_type LIKE'%StandalonePostgresql'AND t.taggable_id IN(SELECT id FROM standalone_postgresqls WHERE environment_id = ANY(env_ids)))OR(t.taggable_type LIKE'%StandaloneRedis'AND t.taggable_id IN(SELECT id FROM standalone_redis WHERE environment_id = ANY(env_ids)))OR(t.taggable_type LIKE'%StandaloneMongodb'AND t.taggable_id IN(SELECT id FROM standalone_mongodbs WHERE environment_id = ANY(env_ids)))OR(t.taggable_type LIKE'%StandaloneMysql'AND t.taggable_id IN(SELECT id FROM standalone_mysqls WHERE environment_id = ANY(env_ids)))OR(t.taggable_type LIKE'%StandaloneMariadb'AND t.taggable_id IN(SELECT id FROM standalone_mariadbs WHERE environment_id = ANY(env_ids)));
IF tag_ids ISNOTNULLAND array_length(tag_ids,1) > 0THENUPDATE tags SET team_id = new_team WHERE id = ANY(tag_ids)AND(team_id = old_team OR team_id ISNULL);
ENDIF;
UPDATE scheduled_database_backups sdb SET team_id = new_team
WHERE sdb.team_id = old_team
AND((sdb.database_type LIKE'%StandalonePostgresql'AND sdb.database_id IN(SELECT id FROM standalone_postgresqls WHERE environment_id = ANY(env_ids)))OR(sdb.database_type LIKE'%StandaloneRedis'AND sdb.database_id IN(SELECT id FROM standalone_redis WHERE environment_id = ANY(env_ids)))OR(sdb.database_type LIKE'%StandaloneMongodb'AND sdb.database_id IN(SELECT id FROM standalone_mongodbs WHERE environment_id = ANY(env_ids)))OR(sdb.database_type LIKE'%StandaloneMysql'AND sdb.database_id IN(SELECT id FROM standalone_mysqls WHERE environment_id = ANY(env_ids)))OR(sdb.database_type LIKE'%StandaloneMariadb'AND sdb.database_id IN(SELECT id FROM standalone_mariadbs WHERE environment_id = ANY(env_ids))));
UPDATE scheduled_tasks st SET team_id = new_team
WHERE st.team_id = old_team
AND(st.application_id IN(SELECT id FROM applications WHERE environment_id = ANY(env_ids))OR st.service_id IN(SELECT id FROM services WHERE environment_id = ANY(env_ids)));
UPDATE shared_environment_variables SET team_id = new_team
WHERE team_id = old_team AND(project_id = project_id_to_move OR environment_id = ANY(env_ids));
RAISE NOTICE 'Migration of project % to team % completed.', project_id_to_move, new_team;
END $$;
-- Verify before COMMIT:-- SELECT id, name, team_id FROM projects WHERE id = 10;-- SELECT id, name, team_id FROM servers WHERE team_id = 11;COMMIT;
-- On failure: ROLLBACK;
BEGIN;
DO $$
DECLARE
project_id_to_move bigint := 10; -- <-- CHANGE
old_team bigint := 0; -- <-- CHANGE
new_team bigint := 11; -- <-- CHANGE
env_ids bigint[];
server_ids bigint[];
key_ids bigint[];
github_ids bigint[];
gitlab_ids bigint[];
s3_ids bigint[];
tag_ids bigint[];
BEGINSELECT array_agg(id)INTO env_ids FROM environments WHERE project_id = project_id_to_move;
UPDATE projects SET team_id = new_team WHERE id = project_id_to_move AND team_id = old_team;
WITH dest_servers AS(SELECT sd.server_id FROM applications a
JOIN environments e ON e.id = a.environment_id AND e.project_id = project_id_to_move
JOIN standalone_dockers sd ON a.destination_type LIKE'%StandaloneDocker'AND a.destination_id = sd.id
UNIONSELECT sw.server_id FROM applications a
JOIN environments e ON e.id = a.environment_id AND e.project_id = project_id_to_move
JOIN swarm_dockers sw ON a.destination_type LIKE'%SwarmDocker'AND a.destination_id = sw.id
UNIONSELECT sd.server_id FROM services srv
JOIN environments e ON e.id = srv.environment_id AND e.project_id = project_id_to_move
JOIN standalone_dockers sd ON srv.destination_type LIKE'%StandaloneDocker'AND srv.destination_id = sd.id
UNIONSELECT sw.server_id FROM services srv
JOIN environments e ON e.id = srv.environment_id AND e.project_id = project_id_to_move
JOIN swarm_dockers sw ON srv.destination_type LIKE'%SwarmDocker'AND srv.destination_id = sw.id
UNIONSELECT sd.server_id FROM standalone_postgresqls sp
JOIN environments e ON e.id = sp.environment_id AND e.project_id = project_id_to_move
JOIN standalone_dockers sd ON sp.destination_type LIKE'%StandaloneDocker'AND sp.destination_id = sd.id
UNIONSELECT sw.server_id FROM standalone_postgresqls sp
JOIN environments e ON e.id = sp.environment_id AND e.project_id = project_id_to_move
JOIN swarm_dockers sw ON sp.destination_type LIKE'%SwarmDocker'AND sp.destination_id = sw.id
)SELECT array_agg(DISTINCT server_id)INTO server_ids FROM dest_servers;
-- Exclude localhost (id=0) or other servers — edit as neededIF server_ids ISNOTNULLAND array_length(server_ids,1) > 0THEN
server_ids := array_remove(server_ids,0::bigint);
ENDIF;
IF server_ids ISNOTNULLAND array_length(server_ids,1) > 0THENUPDATE servers SET team_id = new_team WHERE id = ANY(server_ids)AND team_id = old_team;
ENDIF;
SELECT array_agg(DISTINCT private_key_id)INTO key_ids
FROM applications WHERE environment_id = ANY(env_ids)AND private_key_id ISNOTNULL;
IF key_ids ISNOTNULLAND array_length(key_ids,1) > 0THENUPDATE private_keys SET team_id = new_team WHERE id = ANY(key_ids)AND team_id = old_team;
ENDIF;
SELECT array_agg(DISTINCT a.source_id)INTO github_ids
FROM applications a WHERE a.environment_id = ANY(env_ids)AND a.source_type ISNOTNULLAND a.source_type LIKE'%GitHub%';
IF github_ids ISNOTNULLAND array_length(github_ids,1) > 0THENUPDATE github_apps SET team_id = new_team WHERE id = ANY(github_ids)AND team_id = old_team;
ENDIF;
SELECT array_agg(DISTINCT a.source_id)INTO gitlab_ids
FROM applications a WHERE a.environment_id = ANY(env_ids)AND a.source_type ISNOTNULLAND a.source_type LIKE'%GitLab%';
IF gitlab_ids ISNOTNULLAND array_length(gitlab_ids,1) > 0THENUPDATE gitlab_apps SET team_id = new_team WHERE id = ANY(gitlab_ids)AND team_id = old_team;
ENDIF;
WITH backup_s3 AS(SELECT sdb.s3_storage_id FROM scheduled_database_backups sdb
JOIN standalone_postgresqls sp ON sdb.database_type LIKE'%StandalonePostgresql'AND sdb.database_id = sp.id AND sp.environment_id = ANY(env_ids)WHERE sdb.s3_storage_id ISNOTNULLUNIONSELECT sdb.s3_storage_id FROM scheduled_database_backups sdb
JOIN standalone_redis sr ON sdb.database_type LIKE'%StandaloneRedis'AND sdb.database_id = sr.id AND sr.environment_id = ANY(env_ids)WHERE sdb.s3_storage_id ISNOTNULLUNIONSELECT sdb.s3_storage_id FROM scheduled_database_backups sdb
JOIN standalone_mongodbs sm ON sdb.database_type LIKE'%StandaloneMongodb'AND sdb.database_id = sm.id AND sm.environment_id = ANY(env_ids)WHERE sdb.s3_storage_id ISNOTNULLUNIONSELECT sdb.s3_storage_id FROM scheduled_database_backups sdb
JOIN standalone_mysqls sm ON sdb.database_type LIKE'%StandaloneMysql'AND sdb.database_id = sm.id AND sm.environment_id = ANY(env_ids)WHERE sdb.s3_storage_id ISNOTNULLUNIONSELECT sdb.s3_storage_id FROM scheduled_database_backups sdb
JOIN standalone_mariadbs sm ON sdb.database_type LIKE'%StandaloneMariadb'AND sdb.database_id = sm.id AND sm.environment_id = ANY(env_ids)WHERE sdb.s3_storage_id ISNOTNULL)SELECT array_agg(DISTINCT s3_storage_id)INTO s3_ids FROM backup_s3 WHERE s3_storage_id ISNOTNULL;
IF s3_ids ISNOTNULLAND array_length(s3_ids,1) > 0THENUPDATE s3_storages SET team_id = new_team WHERE id = ANY(s3_ids)AND team_id = old_team;
ENDIF;
SELECT array_agg(DISTINCT t.tag_id)INTO tag_ids FROM taggables t
WHERE(t.taggable_type LIKE'%Application'AND t.taggable_id IN(SELECT id FROM applications WHERE environment_id = ANY(env_ids)))OR(t.taggable_type LIKE'%Service'AND t.taggable_id IN(SELECT id FROM services WHERE environment_id = ANY(env_ids)))OR(t.taggable_type LIKE'%StandalonePostgresql'AND t.taggable_id IN(SELECT id FROM standalone_postgresqls WHERE environment_id = ANY(env_ids)))OR(t.taggable_type LIKE'%StandaloneRedis'AND t.taggable_id IN(SELECT id FROM standalone_redis WHERE environment_id = ANY(env_ids)))OR(t.taggable_type LIKE'%StandaloneMongodb'AND t.taggable_id IN(SELECT id FROM standalone_mongodbs WHERE environment_id = ANY(env_ids)))OR(t.taggable_type LIKE'%StandaloneMysql'AND t.taggable_id IN(SELECT id FROM standalone_mysqls WHERE environment_id = ANY(env_ids)))OR(t.taggable_type LIKE'%StandaloneMariadb'AND t.taggable_id IN(SELECT id FROM standalone_mariadbs WHERE environment_id = ANY(env_ids)));
IF tag_ids ISNOTNULLAND array_length(tag_ids,1) > 0THENUPDATE tags SET team_id = new_team WHERE id = ANY(tag_ids)AND(team_id = old_team OR team_id ISNULL);
ENDIF;
UPDATE scheduled_database_backups sdb SET team_id = new_team
WHERE sdb.team_id = old_team
AND((sdb.database_type LIKE'%StandalonePostgresql'AND sdb.database_id IN(SELECT id FROM standalone_postgresqls WHERE environment_id = ANY(env_ids)))OR(sdb.database_type LIKE'%StandaloneRedis'AND sdb.database_id IN(SELECT id FROM standalone_redis WHERE environment_id = ANY(env_ids)))OR(sdb.database_type LIKE'%StandaloneMongodb'AND sdb.database_id IN(SELECT id FROM standalone_mongodbs WHERE environment_id = ANY(env_ids)))OR(sdb.database_type LIKE'%StandaloneMysql'AND sdb.database_id IN(SELECT id FROM standalone_mysqls WHERE environment_id = ANY(env_ids)))OR(sdb.database_type LIKE'%StandaloneMariadb'AND sdb.database_id IN(SELECT id FROM standalone_mariadbs WHERE environment_id = ANY(env_ids))));
UPDATE scheduled_tasks st SET team_id = new_team
WHERE st.team_id = old_team
AND(st.application_id IN(SELECT id FROM applications WHERE environment_id = ANY(env_ids))OR st.service_id IN(SELECT id FROM services WHERE environment_id = ANY(env_ids)));
UPDATE shared_environment_variables SET team_id = new_team
WHERE team_id = old_team AND(project_id = project_id_to_move OR environment_id = ANY(env_ids));
RAISE NOTICE 'Migration of project % to team % completed.', project_id_to_move, new_team;
END $$;
-- Verify before COMMIT:-- SELECT id, name, team_id FROM projects WHERE id = 10;-- SELECT id, name, team_id FROM servers WHERE team_id = 11;COMMIT;
-- On failure: ROLLBACK;
BEGIN;
DO $$
DECLARE
project_id_to_move bigint := 10; -- <-- CHANGE
old_team bigint := 0; -- <-- CHANGE
new_team bigint := 11; -- <-- CHANGE
env_ids bigint[];
server_ids bigint[];
key_ids bigint[];
github_ids bigint[];
gitlab_ids bigint[];
s3_ids bigint[];
tag_ids bigint[];
BEGINSELECT array_agg(id)INTO env_ids FROM environments WHERE project_id = project_id_to_move;
UPDATE projects SET team_id = new_team WHERE id = project_id_to_move AND team_id = old_team;
WITH dest_servers AS(SELECT sd.server_id FROM applications a
JOIN environments e ON e.id = a.environment_id AND e.project_id = project_id_to_move
JOIN standalone_dockers sd ON a.destination_type LIKE'%StandaloneDocker'AND a.destination_id = sd.id
UNIONSELECT sw.server_id FROM applications a
JOIN environments e ON e.id = a.environment_id AND e.project_id = project_id_to_move
JOIN swarm_dockers sw ON a.destination_type LIKE'%SwarmDocker'AND a.destination_id = sw.id
UNIONSELECT sd.server_id FROM services srv
JOIN environments e ON e.id = srv.environment_id AND e.project_id = project_id_to_move
JOIN standalone_dockers sd ON srv.destination_type LIKE'%StandaloneDocker'AND srv.destination_id = sd.id
UNIONSELECT sw.server_id FROM services srv
JOIN environments e ON e.id = srv.environment_id AND e.project_id = project_id_to_move
JOIN swarm_dockers sw ON srv.destination_type LIKE'%SwarmDocker'AND srv.destination_id = sw.id
UNIONSELECT sd.server_id FROM standalone_postgresqls sp
JOIN environments e ON e.id = sp.environment_id AND e.project_id = project_id_to_move
JOIN standalone_dockers sd ON sp.destination_type LIKE'%StandaloneDocker'AND sp.destination_id = sd.id
UNIONSELECT sw.server_id FROM standalone_postgresqls sp
JOIN environments e ON e.id = sp.environment_id AND e.project_id = project_id_to_move
JOIN swarm_dockers sw ON sp.destination_type LIKE'%SwarmDocker'AND sp.destination_id = sw.id
)SELECT array_agg(DISTINCT server_id)INTO server_ids FROM dest_servers;
-- Exclude localhost (id=0) or other servers — edit as neededIF server_ids ISNOTNULLAND array_length(server_ids,1) > 0THEN
server_ids := array_remove(server_ids,0::bigint);
ENDIF;
IF server_ids ISNOTNULLAND array_length(server_ids,1) > 0THENUPDATE servers SET team_id = new_team WHERE id = ANY(server_ids)AND team_id = old_team;
ENDIF;
SELECT array_agg(DISTINCT private_key_id)INTO key_ids
FROM applications WHERE environment_id = ANY(env_ids)AND private_key_id ISNOTNULL;
IF key_ids ISNOTNULLAND array_length(key_ids,1) > 0THENUPDATE private_keys SET team_id = new_team WHERE id = ANY(key_ids)AND team_id = old_team;
ENDIF;
SELECT array_agg(DISTINCT a.source_id)INTO github_ids
FROM applications a WHERE a.environment_id = ANY(env_ids)AND a.source_type ISNOTNULLAND a.source_type LIKE'%GitHub%';
IF github_ids ISNOTNULLAND array_length(github_ids,1) > 0THENUPDATE github_apps SET team_id = new_team WHERE id = ANY(github_ids)AND team_id = old_team;
ENDIF;
SELECT array_agg(DISTINCT a.source_id)INTO gitlab_ids
FROM applications a WHERE a.environment_id = ANY(env_ids)AND a.source_type ISNOTNULLAND a.source_type LIKE'%GitLab%';
IF gitlab_ids ISNOTNULLAND array_length(gitlab_ids,1) > 0THENUPDATE gitlab_apps SET team_id = new_team WHERE id = ANY(gitlab_ids)AND team_id = old_team;
ENDIF;
WITH backup_s3 AS(SELECT sdb.s3_storage_id FROM scheduled_database_backups sdb
JOIN standalone_postgresqls sp ON sdb.database_type LIKE'%StandalonePostgresql'AND sdb.database_id = sp.id AND sp.environment_id = ANY(env_ids)WHERE sdb.s3_storage_id ISNOTNULLUNIONSELECT sdb.s3_storage_id FROM scheduled_database_backups sdb
JOIN standalone_redis sr ON sdb.database_type LIKE'%StandaloneRedis'AND sdb.database_id = sr.id AND sr.environment_id = ANY(env_ids)WHERE sdb.s3_storage_id ISNOTNULLUNIONSELECT sdb.s3_storage_id FROM scheduled_database_backups sdb
JOIN standalone_mongodbs sm ON sdb.database_type LIKE'%StandaloneMongodb'AND sdb.database_id = sm.id AND sm.environment_id = ANY(env_ids)WHERE sdb.s3_storage_id ISNOTNULLUNIONSELECT sdb.s3_storage_id FROM scheduled_database_backups sdb
JOIN standalone_mysqls sm ON sdb.database_type LIKE'%StandaloneMysql'AND sdb.database_id = sm.id AND sm.environment_id = ANY(env_ids)WHERE sdb.s3_storage_id ISNOTNULLUNIONSELECT sdb.s3_storage_id FROM scheduled_database_backups sdb
JOIN standalone_mariadbs sm ON sdb.database_type LIKE'%StandaloneMariadb'AND sdb.database_id = sm.id AND sm.environment_id = ANY(env_ids)WHERE sdb.s3_storage_id ISNOTNULL)SELECT array_agg(DISTINCT s3_storage_id)INTO s3_ids FROM backup_s3 WHERE s3_storage_id ISNOTNULL;
IF s3_ids ISNOTNULLAND array_length(s3_ids,1) > 0THENUPDATE s3_storages SET team_id = new_team WHERE id = ANY(s3_ids)AND team_id = old_team;
ENDIF;
SELECT array_agg(DISTINCT t.tag_id)INTO tag_ids FROM taggables t
WHERE(t.taggable_type LIKE'%Application'AND t.taggable_id IN(SELECT id FROM applications WHERE environment_id = ANY(env_ids)))OR(t.taggable_type LIKE'%Service'AND t.taggable_id IN(SELECT id FROM services WHERE environment_id = ANY(env_ids)))OR(t.taggable_type LIKE'%StandalonePostgresql'AND t.taggable_id IN(SELECT id FROM standalone_postgresqls WHERE environment_id = ANY(env_ids)))OR(t.taggable_type LIKE'%StandaloneRedis'AND t.taggable_id IN(SELECT id FROM standalone_redis WHERE environment_id = ANY(env_ids)))OR(t.taggable_type LIKE'%StandaloneMongodb'AND t.taggable_id IN(SELECT id FROM standalone_mongodbs WHERE environment_id = ANY(env_ids)))OR(t.taggable_type LIKE'%StandaloneMysql'AND t.taggable_id IN(SELECT id FROM standalone_mysqls WHERE environment_id = ANY(env_ids)))OR(t.taggable_type LIKE'%StandaloneMariadb'AND t.taggable_id IN(SELECT id FROM standalone_mariadbs WHERE environment_id = ANY(env_ids)));
IF tag_ids ISNOTNULLAND array_length(tag_ids,1) > 0THENUPDATE tags SET team_id = new_team WHERE id = ANY(tag_ids)AND(team_id = old_team OR team_id ISNULL);
ENDIF;
UPDATE scheduled_database_backups sdb SET team_id = new_team
WHERE sdb.team_id = old_team
AND((sdb.database_type LIKE'%StandalonePostgresql'AND sdb.database_id IN(SELECT id FROM standalone_postgresqls WHERE environment_id = ANY(env_ids)))OR(sdb.database_type LIKE'%StandaloneRedis'AND sdb.database_id IN(SELECT id FROM standalone_redis WHERE environment_id = ANY(env_ids)))OR(sdb.database_type LIKE'%StandaloneMongodb'AND sdb.database_id IN(SELECT id FROM standalone_mongodbs WHERE environment_id = ANY(env_ids)))OR(sdb.database_type LIKE'%StandaloneMysql'AND sdb.database_id IN(SELECT id FROM standalone_mysqls WHERE environment_id = ANY(env_ids)))OR(sdb.database_type LIKE'%StandaloneMariadb'AND sdb.database_id IN(SELECT id FROM standalone_mariadbs WHERE environment_id = ANY(env_ids))));
UPDATE scheduled_tasks st SET team_id = new_team
WHERE st.team_id = old_team
AND(st.application_id IN(SELECT id FROM applications WHERE environment_id = ANY(env_ids))OR st.service_id IN(SELECT id FROM services WHERE environment_id = ANY(env_ids)));
UPDATE shared_environment_variables SET team_id = new_team
WHERE team_id = old_team AND(project_id = project_id_to_move OR environment_id = ANY(env_ids));
RAISE NOTICE 'Migration of project % to team % completed.', project_id_to_move, new_team;
END $$;
-- Verify before COMMIT:-- SELECT id, name, team_id FROM projects WHERE id = 10;-- SELECT id, name, team_id FROM servers WHERE team_id = 11;COMMIT;
-- On failure: ROLLBACK;
5.4 Pre-migration state check
Overview of resource counts per team (before migrating an entire team or before a dry-run):
If the “localhost” server (id=0) was moved to another team and should remain in team 0:
UPDATE servers
SET team_id = 0WHERE id = 0AND team_id = 11;
SELECT id, name, team_id FROM servers WHERE id = 0
UPDATE servers
SET team_id = 0WHERE id = 0AND team_id = 11;
SELECT id, name, team_id FROM servers WHERE id = 0
UPDATE servers
SET team_id = 0WHERE id = 0AND team_id = 11;
SELECT id, name, team_id FROM servers WHERE id = 0
No other tables store the server’s team membership — only servers.team_id. Relations (applications, services, standalone_dockers) use server_id; after reverting the team, the server continues to work; only its visibility in the Coolify UI changes.
6.2 Reverting an entire migration
If the migration ran in a single transaction and COMMIT was not executed, run ROLLBACK.
If COMMIT was already run, restoring the previous state requires a database restore from backup or manual UPDATEs in the opposite direction (using saved dry-run results and backup).
7. UI verification and cache
After migration, the project and servers have the target team_id in the database. In Coolify, switch the team view to the target team (e.g. “A Team”) — the project and servers are visible only there.
If the UI still shows the old state: clear cache (e.g. php artisan cache:clear, php artisan config:clear in the Coolify container) and restart the Coolify container; hard-refresh the browser (Ctrl+Shift+R).
Team access: the user must be in team_user for the target team. Check with: SELECT * FROM team_user WHERE team_id = 11;. If needed, add a row (e.g. INSERT INTO team_user (team_id, user_id, role) VALUES (11, <user_id>, 'member');).