Actual Budget на сервере: частые ошибки и решения
Actual Budget поднимается одним docker-compose файлом за пять минут, и в этом его ловушка: кажется, что дальше всё «просто работает», а потом клиент внезапно не видит сервер, бюджет исчезает после перезапуска контейнера или синхронизация ломается после обновления. Ниже — конкретные причины таких сбоев и то, как их закрыть раз и навсегда, без танцев с бубном при каждом деплое.
Содержание
- Как устроен self-hosted Actual Budget и что вообще может сломаться
- «Unable to connect to server»: почему без HTTPS клиент не подключится
- Permission denied при первом запуске тома с данными
- Бюджет «пропадает» после перезапуска контейнера
- После обновления сервер не синхронизируется с клиентом
- Бэкап и восстановление данных
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Как устроен self-hosted Actual Budget и что вообще может сломаться
Actual Budget — это envelope-бюджетирование (метод конвертов) с быстрым локальным интерфейсом: приложение работает как PWA/desktop-клиент, хранит копию бюджета у себя и синхронизирует зашифрованные снэпшоты через ваш сервер. Сервер здесь — не «облако с логикой», а тонкий релей: он принимает и раздаёт зашифрованные блоки данных, сам их не читает. Это важно держать в голове, потому что часть ошибок ниже — не баги Actual, а следствия этой архитектуры (нужен HTTPS для криптографии в браузере, сервер не может «расшифровать и починить» ваш бюджет).
Официальный образ — actualbudget/actual-server, слушает порт 5006, все данные лежат в одном томе /data: своя SQLite-база для пароля администратора и папки с синхронизированными файлами бюджета. Минимальный рабочий docker-compose.yml:
services:
actual:
image: docker.io/actualbudget/actual-server:latest
restart: unless-stopped
ports:
- "5006:5006"
volumes:
- ./actual-data:/data
Этого хватает для локального теста. Для прод-эксплуатации на арендованном сервере нужны ещё три вещи: TLS перед приложением, правильные права на том с данными и осознанная стратегия бэкапов и апдейтов — именно тут возникает большинство обращений в поддержку. Если сравниваете с альтернативами, у нас есть отдельный разбор Firefly III — более «бухгалтерского» инструмента с двойной записью, если envelope-подход не подходит.
«Unable to connect to server»: почему без HTTPS клиент не подключится
Самая частая жалоба — при добавлении custom-сервера в приложении бесконечная загрузка или прямая ошибка соединения, хотя curl http://ваш-ip:5006 с сервера отвечает нормально. Причина почти всегда одна: клиент Actual — это PWA, и часть криптографии (Web Crypto API), на которой держится шифрование бюджета, браузер разрешает выполнять только в «безопасном контексте» — то есть на HTTPS (или на localhost). Отдать сервер по голому HTTP на внешний IP — верный способ получить рабочий бэкенд и молчаливо отваливающийся клиент.
Решение — reverse proxy с автоматическим TLS перед контейнером. Пример на Caddy (самый безболезненный вариант для одного домена):
budget.example.com {
reverse_proxy localhost:5006
}
Caddy сам получит и продлит сертификат Let's Encrypt. Если уже используете nginx для других сайтов на сервере, конфиг для Actual выглядит так:
server {
listen 443 ssl http2;
server_name budget.example.com;
ssl_certificate /etc/letsencrypt/live/budget.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/budget.example.com/privkey.pem;
client_max_body_size 20m;
proxy_read_timeout 120s;
location / {
proxy_pass http://127.0.0.1:5006;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Две детали, которые часто забывают и потом ловят непонятные обрывы: client_max_body_size — при импорте большого бюджета или загрузке файла сервер получает multipart-запрос заметно больше дефолтного лимита nginx в 1 МБ; и proxy_read_timeout — на слабом сервере первичная синхронизация крупного бюджета может занять больше стандартных 60 секунд. Если самостоятельная настройка TLS и прокси кажется избыточной возней, у нас есть пошаговый разбор Caddy с авто-SSL: частые ошибки и решения — закрывает почти все грабли с сертификатами разом.
Отдельно: если сервер стоит за Cloudflare или другим CDN-проксированием, добавьте переменную ACTUAL_TRUSTED_PROXIES со списком IP или подсетей прокси в окружение контейнера — иначе Actual может некорректно определять исходный IP клиента и это иногда мешает работе rate-limit'а на логине.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверPermission denied при первом запуске тома с данными
Контейнер actual-server работает не под root, а под непривилегированным пользователем внутри образа. Если вы просто указали bind-mount на директорию, которую Docker создал автоматически (владелец — root хоста), процесс внутри контейнера не сможет писать в /data и в логах будет что-то вроде:
Error: EACCES: permission denied, open '/data/account.sqlite'
Контейнер при этом может даже подняться и слушать порт, но любая попытка сохранить бюджет или создать первого пользователя провалится. Фикс — создать каталог заранее и выставить права под UID контейнера (обычно 1000):
mkdir -p ./actual-data
sudo chown -R 1000:1000 ./actual-data
docker compose up -d
docker compose logs -f actual
Если не хотите гадать с UID — используйте именованный том вместо bind-mount, Docker сам управляет правами внутри него:
volumes:
actual-data:
services:
actual:
image: docker.io/actualbudget/actual-server:latest
restart: unless-stopped
ports:
- "127.0.0.1:5006:5006"
volumes:
- actual-data:/data
Обратите внимание и на пробрасывание порта только на 127.0.0.1 — если TLS уже терминируется на reverse proxy, слушать 5006 «наружу» на всех интерфейсах незачем, это лишняя поверхность атаки.
Бюджет «пропадает» после перезапуска контейнера
Классический сценарий: развернули Actual командой docker run для теста без -v, всё поработало день, потом сервер перезагрузили (обновление ОС, ребут VPS) — и бюджет пуст, будто вы только что установили приложение. Без явного тома данные живут в writable-слое контейнера и исчезают при его пересоздании (а не только при удалении — пересоздание при docker run без --name и повторном docker run тоже создаёт новый контейнер).
Проверить, что том реально примонтирован, а не потерян где-то по пути:
docker inspect actual | grep -A 5 '"Mounts"'
Если в выводе нет вашего пути или именованного тома — деплой не персистентный, чините compose-файл как в разделе выше и переносите то, что осталось, из старого writable-слоя контейнера (docker cp <container>:/data ./actual-data) до того, как контейнер будет удалён. Также проверьте, что на сервере не крутится случайно два инстанса Actual на одном порту из-за незакрытого предыдущего деплоя — второй контейнер с портом 5006 просто не поднимется, но при других портах путаница между «старым» и «новым» инстансом с разными томами данных — частая причина ощущения «бюджет пропал», хотя на деле он просто в другом месте.
После обновления сервер не синхронизируется с клиентом
Actual развивается быстро, и протокол синхронизации иногда меняется вместе с версией. Если обновили сервер (docker compose pull && docker compose up -d) командой, которая тянет :latest, а клиент в браузере остался на закешированной старой версии service worker'а — получите ошибку вида «This budget was created with a different version of Actual» или зависшую синхронизацию без внятного текста.
Порядок действий при апдейте, который снижает риск:
- Перед обновлением сделайте бэкап
/data(раздел ниже) — это дешевле, чем откатываться по памяти. - Обновите сервер и проверьте логи на ошибки миграции:
docker compose logs -f actual. - На клиенте сделайте жёсткое обновление страницы (
Ctrl+Shift+R) — PWA держит service worker, обычный F5 его не всегда сбрасывает. - Если используете мобильное или desktop-приложение отдельно от веба — обновите и его, версии клиента и сервера не должны расходиться сильно.
Если держите Actual в проде и не хотите сюрпризов от :latest, зафиксируйте версию тегом при обновлении вручную, а не через автообновление образов — так апдейт происходит осознанно, а не в 3 часа ночи вместе с десятком других контейнеров. Если для автообновлений всё же используете Watchtower, стоит явно исключить сервис Actual из его области действия или свериться с нашим разбором автообновления Watchtower — там как раз про то, как не обновить критичный сервис незаметно для себя.
Бэкап и восстановление данных
Здесь легко ошибиться дважды: сделать бэкап не того, что нужно, и не проверить, что из бэкапа реально можно восстановиться. Экспорт бюджета через интерфейс (File → Export Data → архив .zip) — это снимок конкретного бюджета на конкретный момент, удобно для переноса между инстансами, но это не полноценный бэкап сервера: он не включает пароль администратора и служебную базу авторизации.
Правильный минимум — бэкапить весь том /data целиком:
docker compose stop actual
tar czf actual-backup-$(date +%F).tar.gz -C ./actual-data .
docker compose start actual
Для регулярного автоматического бэкапа без остановки сервиса на живом проде надёжнее полагаться на снапшот-инструменты, которые умеют консистентно бэкапить SQLite без блокировки приложения — у нас есть пошаговые разборы под restic и Borg, если хотите не изобретать cron-скрипт с нуля. Общая логика бэкапа именно docker-томов (не только для Actual) разобрана в статье бэкап Docker volume: частые ошибки и решения — там же про типичную ошибку «бэкапили не тот путь» и про проверку restore.
Восстановление — обратная операция: останавливаем контейнер, разворачиваем архив в пустой ./actual-data, поднимаем сервис заново. Раз в квартал стоит реально проверять восстановление на тестовом сервере, а не полагаться на то, что архив «наверное целый» — это касается любого self-hosted сервиса с финансовыми данными, не только Actual.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Можно ли запустить Actual Budget без Docker?
Да, это обычное Node.js-приложение, его можно поставить напрямую и запускать через systemd или pm2. Docker удобнее тем, что версия сервера и её зависимости изолированы от системного Node на сервере и обновление — это просто смена образа.
Сколько ресурсов сервера нужно под Actual Budget?
Приложение лёгкое, ориентировочно хватает 512 МБ — 1 ГБ оперативной памяти под сам сервис даже с несколькими синхронизирующимися клиентами; точная цифра зависит от размера истории транзакций и от того, что ещё крутится на том же сервере. Гонять его на отдельном VPS ради одного Actual обычно избыточно — удобнее держать его на общем сервере за reverse proxy вместе с другими лёгкими self-hosted сервисами.
Что будет, если забыть пароль шифрования бюджета?
Actual использует end-to-end шифрование с паролем, который сервер не хранит и не знает. Если пароль потерян и нет локальной копии клиента с расшифрованными данными — бюджет не восстановить в принципе, это не «забыл пароль от сайта». Отдельно храните пароль шифрования там же, где храните бэкапы /data.
Безопасно ли открывать порт 5006 напрямую в интернет без reverse proxy?
Не стоит: без TLS ломается сама возможность подключиться клиентом (см. раздел выше), а открытый порт без ограничений — лишняя точка для брутфорса логина. Держите порт на 127.0.0.1, работайте через прокси с HTTPS и настройте файрвол на сервере — базовые правила разобраны в статье про UFW: частые ошибки и решения.
Как перенести бюджет с облачной версии Actual на self-hosted сервер?
Через тот же механизм экспорта/импорта: экспортируете .zip с бюджетом из облачного аккаунта, поднимаете свой сервер, при первом входе создаёте нового пользователя и импортируете файл. История транзакций и настройки категорий переносятся вместе с файлом.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →