TeamSpeak в контейнере — это официальный образ, один compose-файл и сервер, который поднимается за минуту на любой машине с Docker. Эта инструкция проведёт от установки Docker до работающего сервера с томами, бэкапом и обновлением без потери данных — и отдельно разберёт грабли с портом 30033, из-за которых в контейнерных установках не работает передача файлов.
Сначала честный вопрос: а нужен ли вам Docker вообще?
Docker оправдан, когда на сервере уже крутятся другие контейнеры и TeamSpeak встраивается в общий docker-compose; когда нужно несколько изолированных экземпляров на одной машине; когда важно разворачивать окружение одной командой из файла в git. Контейнер фиксирует версию, не мусорит в систему и сносится бесследно.
Docker избыточен, если у вас один выделенный VDS под один голосовой сервер. TeamSpeak — статический бинарник без зависимостей: ему не нужна изоляция библиотек, ради которой обычно и берут контейнеры. Docker здесь добавляет слой абстракции, сетевой NAT и новые точки отказа, а взамен даёт немногое. Если вы выбираете способ запуска с нуля, сравните его с Ubuntu и Windows в обзоре создания сервера TeamSpeak. Для одного VDS быстрее и надёжнее обычная установка на Ubuntu — с systemd и без прослоек.
Если решили в пользу Docker — поехали. Понадобится VDS с Ubuntu 24.04 или 26.04, root-доступ и 15 минут. Для нового VDS выбирайте 26.04, а перед миграцией существующего сервера сверяйтесь с разбором Ubuntu 24.04 и 26.04. Подготовка машины и обновление пакетов описаны в инструкции по Ubuntu, в первом шаге.
Устанавливаем Docker и Compose
На Ubuntu 24.04 всё нужное есть в штатных репозиториях:
apt update
apt install -y docker.io docker-compose-v2
Проверяем, что демон запущен и отвечает:
docker version
В выводе должны быть блоки Client и Server. Если хотите самые свежие версии Docker — ставьте из официального репозитория Docker по инструкции с docs.docker.com, но для TeamSpeak возможностей штатного пакета хватает с запасом.
Пишем docker-compose.yml
Создаём каталог проекта и конфиг:
mkdir -p /opt/teamspeak-docker && cd /opt/teamspeak-docker
Файл /opt/teamspeak-docker/docker-compose.yml:
services:
teamspeak:
# Официальный образ с Docker Hub. Тег фиксируем на конкретной
# версии, а не latest: обновление сервера должно быть осознанным
# действием, а не сюрпризом при пересоздании контейнера.
image: teamspeak:3.13.8
# Имя контейнера, чтобы в командах писать teamspeak,
# а не сгенерированное teamspeak-docker-teamspeak-1.
container_name: teamspeak
# Автозапуск: контейнер поднимается после перезагрузки VDS
# и после падения процесса. Аналог systemd-юнита с Restart=.
restart: unless-stopped
ports:
# Голос. Обязательно /udp — по TCP клиенты не подключатся.
- "9987:9987/udp"
# Передача файлов, аватарки, иконки. СТРОГО один в один —
# почему, разобрано в следующем шаге.
- "30033:30033"
# ServerQuery (10011/tcp) наружу НЕ публикуем: это полный
# админ-доступ с нешифрованным паролем. Нет строки — нет порта.
environment:
# Без принятия лицензии сервер не стартует и завершится
# с ошибкой в логе. Значение view вместо accept печатает
# текст соглашения.
TS3SERVER_LICENSE: accept
volumes:
# Всё состояние сервера — база, файлы, логи — живёт
# в /var/ts3server внутри контейнера. Монтируем наружу,
# иначе данные умрут вместе с контейнером.
- ./ts3data:/var/ts3server
Здесь использован bind mount (./ts3data) — каталог рядом с compose-файлом. Данные видны обычными командами, их просто бэкапить. Альтернатива — именованный том (ts3data: плюс секция volumes: в конце файла): им управляет Docker, и с правами доступа проблем меньше — об этом в шаге про грабли.
Пробрасываем порт 30033 один в один
Это самый важный шаг статьи, и почти ни одна инструкция его не объясняет.
С голосовым портом 9987 можно делать что угодно: опубликуйте его наружу как 9988 ("9988:9987/udp") — и клиенты, подключаясь на адрес:9988, будут прекрасно работать. С портом передачи файлов 30033 этот фокус не пройдёт: файлы, аватарки и иконки молча перестанут ходить, а голос при этом останется в порядке — поэтому проблему замечают не сразу и ищут не там.
Причина в устройстве протокола. Передача файлов в TeamSpeak идёт не через голосовое соединение, а через отдельное TCP-подключение. Когда клиент запрашивает файл, сервер отвечает служебным сообщением: ключ передачи и номер порта, на котором слушает он сам — тот, что задан в его конфигурации, по умолчанию 30033. Клиент открывает новое TCP-соединение на этот порт по адресу сервера.
Теперь смотрите, что происходит при кривом пробросе "30044:30033". Внутри контейнера сервер слушает 30033 и честно называет клиенту порт 30033. Клиент подключается на внешний-адрес:30033 — а там ничего нет: снаружи Docker опубликовал 30044, о котором сервер не знает. Соединение отваливается, файлы не ходят.
Отсюда правило: внешний и внутренний номер порта передачи файлов обязаны совпадать. Либо стандартно:
ports:
- "30033:30033"
Либо, если 30033 на хосте занят, меняем порт и внутри контейнера тоже — переменной окружения, которую entrypoint официального образа превращает в параметр filetransfer_port конфига:
ports:
- "30044:30044"
environment:
TS3SERVER_LICENSE: accept
TS3SERVER_FILETRANSFER_PORT: "30044"
Тогда сервер и слушает 30044, и называет клиенту 30044 — всё сходится. Полная карта портов TeamSpeak с назначением каждого — в справочнике по портам.
Запускаем сервер и сохраняем ключи
Поднимаем:
docker compose up -d
Docker скачает образ и запустит контейнер. Теперь — не откладывая — смотрим лог первого запуска:
docker logs teamspeak
В нём будет блок I M P O R T A N T с тремя строками: loginname= "serveradmin", password= "..." и ниже — token=.... Пароль — это админ-доступ к ServerQuery, token — privilege key, дающий права администратора первому, кто введёт его в клиенте (Permissions → Use Privilege Key).
Сохраните обе строки сейчас. В контейнерной установке это критичнее, чем в обычной: docker logs привязан к конкретному контейнеру. Поменяете compose-файл, выполните docker compose up -d — Docker пересоздаст контейнер, и лог первого запуска исчезнет вместе с токеном. Подстраховка: копия лога попадает и в файлы внутри тома, grep -r "token=" ./ts3data/logs/ найдёт ключ, пока логи не ротировались. Если и ключ использован, и доступ потерян — инструкция по восстановлению работает и для контейнера: ServerQuery доступен изнутри, например docker exec -it teamspeak sh, а дальше как обычно.
Проверяем снаружи, а не по логу: подключитесь клиентом на IP сервера, активируйте privilege key и загрузите аватарку — она проверяет как раз порт 30033.
Настраиваем бэкап
Что копировать — то же, что и без Docker: база ts3server.sqlitedb, каталог files/, файлы конфигурации. Разница в том, что всё это уже собрано в одном месте — томе ./ts3data. Не нужно вспоминать пути: бэкап тома — это бэкап сервера целиком.
Второе отличие: останавливаем не systemd-сервис, а контейнер. Пропускать остановку нельзя — SQLite-базу, в которую сервер пишет, копировать на живую опасно: в архив попадёт несогласованный файл. Скрипт /opt/teamspeak-docker/backup.sh:
#!/bin/bash
set -euo pipefail
BACKUP_DIR=/var/backups/teamspeak
STAMP=$(date +%F_%H-%M)
mkdir -p "$BACKUP_DIR"
cd /opt/teamspeak-docker
docker compose stop
tar czf "$BACKUP_DIR/ts3-docker-$STAMP.tar.gz" ts3data docker-compose.yml
docker compose start
find "$BACKUP_DIR" -name 'ts3-docker-*.tar.gz' -mtime +14 -delete
echo "OK: $BACKUP_DIR/ts3-docker-$STAMP.tar.gz"
В архив кладём и docker-compose.yml: вместе с томом он делает бэкап самодостаточным — на любой машине с Docker распаковали, docker compose up -d, сервер жив. Это одновременно и процедура переезда; подробно о переносе сервера со всеми настройками — в отдельной статье.
Скрипт — в cron на ночное время (chmod +x backup.sh, затем crontab -e), и раз в неделю забирайте архив на другую машину: бэкап на том же диске не переживёт смерть VDS.
Обновляем образ без потери данных
Ради этого шага том и выносили наружу. Контейнер — одноразовая обёртка вокруг данных: его можно убить и создать заново, и пока /var/ts3server смонтирован в том, сервер этого «не заметит».
Порядок обновления. Сначала бэкап (скрипт из предыдущего шага) — обновление базы необратимо, откат на старую версию сервера со свежей базой не поддерживается. Затем в docker-compose.yml меняем тег на новую версию — актуальный список тегов на странице образа teamspeak на Docker Hub, на 7 августа 2026 года свежий из них 3.13.8:
image: teamspeak:3.13.8
И применяем:
docker compose pull
docker compose up -d
Docker скачает новый образ, остановит старый контейнер и создаст новый с тем же томом. Сервер при первом запуске сам мигрирует схему базы. Проверка — снаружи: клиент подключается, каналы и права на месте, аватарка загружается.
Именно поэтому в конфиге зафиксирован точный тег, а не latest: с latest та же команда pull притащила бы новую мажорную версию тогда, когда вы этого не планировали, — без бэкапа и без чтения changelog.
Пробуем TeamSpeak 6 в контейнере
У TeamSpeak 6 Server официальный способ установки — как раз Docker: образ teamspeaksystems/teamspeak6-server на Docker Hub, документация и примеры compose-файлов — в репозитории github.com/teamspeak/teamspeak6-server. Статус — beta: сервер не feature-complete, бета-лицензия даёт 32 слота и продлевается каждые два месяца, лицензии TeamSpeak 3 несовместимы, пути миграции с третьей версии нет. Что такое TeamSpeak 6 и чего от него ждать — в обзоре; здесь — только запуск.
Конфигурация устроена иначе, чем в TS3. Переменные окружения носят префикс TSSERVER_ (без тройки), лицензия принимается переменной TSSERVER_LICENSE_ACCEPTED=accept, данные живут в /var/tsserver, а вместо ts3server.ini — YAML-файл tsserver.yaml; любой параметр можно задать тремя равнозначными способами: аргументом командной строки, переменной окружения или строкой в YAML. Полный список — в CONFIG.md репозитория. Минимальный compose по мотивам официального примера:
services:
teamspeak6:
image: teamspeaksystems/teamspeak6-server:latest
container_name: teamspeak6
restart: unless-stopped
ports:
- "9987:9987/udp" # голос
- "30033:30033" # передача файлов — правило "один в один" никуда не делось
# - "10080:10080" # WebQuery (HTTP API) — включается TSSERVER_QUERY_HTTP_ENABLED
# - "10022:10022" # ServerQuery по SSH — TSSERVER_QUERY_SSH_ENABLED
environment:
TSSERVER_LICENSE_ACCEPTED: accept
volumes:
- ./ts6data:/var/tsserver
Порты голоса и файлов те же, что у TS3, поэтому на одной машине два сервера одновременно потребуют разных внешних портов — и для файлового порта TS6 это значит менять его и внутри, переменной TSSERVER_FILE_TRANSFER_PORT. Классический ServerQuery на 10011 заменили HTTP- и SSH-варианты, по умолчанию выключенные. Privilege key при первом запуске так же печатается в docker logs teamspeak6 — правило «сохранить сразу» в силе.
Обходим типовые грабли
Права на каталог тома. Сервер внутри официального образа работает от пользователя ts3server с UID 9987, а не от root. Если bind mount указывает на каталог, куда UID 9987 писать не может, контейнер падает на старте с Permission denied в логе (классика — ошибка создания ts3server.ini или базы). Лечение: chown -R 9987:9987 /opt/teamspeak-docker/ts3data. Или сразу используйте именованный том — Docker выставит владельца сам при первом монтировании.
Потерянный privilege key. Уже разобрано в шаге про запуск, но повторим, потому что это грабли номер один: docker logs умирает вместе с контейнером, а контейнеры в Docker пересоздаются по любому чиху. Ключ и пароль serveradmin сохраняются в момент первого запуска, не «потом».
Логи. Смотреть их можно двумя способами, и они не дублируют друг друга полностью. docker logs -f teamspeak — stdout процесса: ошибки старта, принятие лицензии, учётные данные первого запуска. Файлы в ts3data/logs/ — содержательные логи самого сервера: подключения, баны, ошибки виртуальных серверов. При разборе проблемы «сервер не стартует» смотрите первый, «сервер работает странно» — вторые.
UDP и фаервол. Docker публикует порты в обход ufw — правила iptables от Docker срабатывают раньше. Из-за этого «закрытый» в ufw порт контейнера может оказаться открытым, и наоборот, включённый ufw не спасает, если вы случайно опубликовали 10011. Правило простое: что не должно торчать наружу — не публикуйте в ports: вовсе, это надёжнее любого фаервола перед контейнером.
Итог: контейнерный TeamSpeak — это compose-файл на двадцать строк, том с данными и три правила, которые не прощают нарушения: порт 30033 пробрасывается один в один, ключи первого запуска сохраняются немедленно, база живёт в томе с первого дня. Всё остальное — обычный уход: бэкап по cron, осознанное обновление тега и проверка восстановления хотя бы раз — до того, как оно понадобится по-настоящему.