As a complement to @Dorin Botan good answer, here is my configuration in tasks.json
, using 2 groups of terminals like this:

{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// See https://code.visualstudio.com/docs/editor/tasks-appendix
"version": "2.0.0",
"tasks": [
{
"label": "Term 1",
"type": "process",
"command": "/bin/bash",
"isBackground": true, // removes the infinite spinner
"problemMatcher": [],
"presentation": {
"group": "main_tasks",
"reveal": "always",
"panel": "new",
"echo": false, // silence "Executing task ..."
}
},
{
"label": "Django server",
"type": "shell",
"command": "./manage.py runserver 127.0.0.1:8000",
"isBackground": true,
"presentation": {
"group": "main_tasks",
"reveal": "always",
"panel": "new",
}
},
{
"label": "Celery worker",
"type": "shell",
"command": "celery --app dj_config worker --uid=nobody --gid=nogroup --loglevel INFO",
"isBackground": true,
"presentation": {
"group": "background_tasks",
"reveal": "never",
"panel": "new",
"showReuseMessage": false,
}
},
{
"label": "Celery beat",
"type": "shell",
"command": "celery --app dj_config beat --uid=nobody --gid=nogroup --loglevel INFO",
"isBackground": true,
"presentation": {
"group": "background_tasks",
"reveal": "never",
"panel": "new",
"showReuseMessage": false,
},
},
{
"label": "Tailwind watcher",
"type": "shell",
"command": "sleep infinity", // FIXME with real command
"isBackground": true,
"presentation": {
"group": "background_tasks",
"reveal": "never",
"panel": "new",
"showReuseMessage": false,
}
},
{
// This is a compound task to run them all
"label": "Mango Terminals (David)",
"dependsOn": [
"Term 1",
"Django server",
"Tailwind watcher",
"Celery worker",
"Celery beat",
],
"dependsOrder": "parallel", // no dependencies between tasks
"problemMatcher": [],
}
]
}