MAATRIX / Блог / Crater в Docker Compose: готовый файл

Crater в Docker Compose: готовый файл

MAATRIX

Если для выставления счетов фрилансеру или небольшой студии хватает пяти полей — клиент, позиции, сумма, срок оплаты, — а Invoice Ninja или тем более ERP-модуль ощущаются как переезд из однушки в бизнес-центр, стоит присмотреться к Crater. Это открытый self-hosted инструмент на Laravel + Vue для инвойсов, смет (estimates) и учёта расходов — без бухгалтерии, зарплатных модулей и десятка вкладок настроек. Ниже — рабочий docker-compose.yml, Dockerfile для сборки образа и порядок первого запуска на чистом VPS.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Что такое Crater и чем он проще Invoice Ninja

Crater закрывает узкий, но частый набор задач малого бизнеса и фрилансера:

  • инвойсы с позициями, налогами, скидками и статусами (черновик / отправлен / оплачен / просрочен);
  • сметы (estimates) с конвертацией в инвойс одним кликом, когда клиент согласовал сумму;
  • учёт расходов (expenses) по категориям и клиентам;
  • карточки клиентов с историей платежей и балансом;
  • ручная фиксация платежей (без встроенного эквайринга — это плюс для тех, кто не хочет тащить платёжный шлюз в закрытый контур);
  • мультивалютность, кастомизация PDF-шаблонов инвойсов, несколько компаний в одной установке.

Чего в Crater нет и не планируется — это как раз то, что делает Invoice Ninja тяжелее: банковские интеграции, отслеживание задач и таймеров, клиентский портал с личным кабинетом сложнее простой ссылки на инвойс. Если вам нужен именно этот функционал, честнее сразу посмотреть пошаговую установку Invoice Ninja — это более зрелый и тяжёлый продукт для тех же целей. Crater имеет смысл, когда нужен инструмент «поставил и забыл»: один процесс, минимум сервисов, простая база.

Технически это классическое Laravel-приложение: PHP-бэкенд, Vue-фронтенд, собираемые в статику при билде, MySQL или MariaDB как база данных. Гарантированно актуального официального образа «всё в одном» на Docker Hub у проекта нет — поэтому ниже собираем образ сами из исходников на GitHub. Дольше, чем docker pull, зато вы контролируете, что именно попадает в контейнер.

Требования к серверу и подготовка Ubuntu

Для одной компании и несерьёзного потока инвойсов (десятки-сотни в месяц) достаточно:

РесурсМинимумКомфортно
CPU1 vCPU2 vCPU
RAM1 ГБ2 ГБ
Диск15 ГБ SSD25+ ГБ SSD
ОСUbuntu 24.04 LTSUbuntu 24.04 LTS

Сборка образа (composer install + npm build) — самый прожорливый по памяти этап, на 1 ГБ RAM без swap она иногда падает с OOM. Если сервер минимальный, добавьте своп перед сборкой:

sudo fallocate -l 1G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

Docker и Compose ставим стандартно:

curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
sudo apt install -y docker-compose-plugin
newgrp docker

Общий подход к продакшен-развёртыванию через Docker Compose — с профилями, healthcheck и порядком запуска — разобран отдельно в статье про Docker Compose для продакшена; ниже — конкретика под Crater.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Собираем образ: Dockerfile для Crater

Создайте структуру проекта:

mkdir -p ~/crater-app/crater
cd ~/crater-app

Внутри crater/Dockerfile:

FROM php:8.2-fpm-alpine

RUN apk add --no-cache \
    nginx supervisor git curl unzip nodejs npm \
    icu-dev libzip-dev libpng-dev oniguruma-dev freetype-dev libjpeg-turbo-dev \
 && docker-php-ext-configure gd --with-freetype --with-jpeg \
 && docker-php-ext-install pdo_mysql intl zip gd bcmath exif opcache

RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

WORKDIR /var/www/html
RUN git clone --depth 1 https://github.com/crater-invoice/crater.git .

RUN composer install --no-dev --optimize-autoloader --no-interaction \
 && npm ci \
 && npm run production \
 && rm -rf node_modules \
 && chown -R www-data:www-data /var/www/html

COPY nginx.conf /etc/nginx/nginx.conf
COPY supervisord.conf /etc/supervisord.conf
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

EXPOSE 80
ENTRYPOINT ["/entrypoint.sh"]

npm run production — команда сборки фронтенда на момент подготовки этой статьи; если в вашей версии репозитория скрипт называется иначе (например, build), посмотрите секцию scripts в package.json и поправьте команду. Точное требование к версии PHP тоже лучше сверить с composer.json (require.php) в клонированном коммите — Crater ориентируется на актуальные ветки PHP 8.x, но фиксировать это числом здесь смысла нет.

crater/nginx.conf:

worker_processes auto;
events { worker_connections 1024; }

http {
    include mime.types;
    sendfile on;

    server {
        listen 80;
        root /var/www/html/public;
        index index.php index.html;
        client_max_body_size 25M;

        location / {
            try_files $uri $uri/ /index.php?$query_string;
        }

        location ~ \.php$ {
            fastcgi_pass 127.0.0.1:9000;
            fastcgi_index index.php;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        }

        location ~ /\.ht { deny all; }
    }
}

crater/supervisord.conf:

[supervisord]
nodaemon=true

[program:php-fpm]
command=php-fpm -F
autostart=true
autorestart=true

[program:nginx]
command=nginx -g "daemon off;"
autostart=true
autorestart=true

crater/entrypoint.sh:

#!/bin/sh
set -e
mkdir -p storage/framework/sessions storage/framework/views storage/framework/cache storage/logs bootstrap/cache
chown -R www-data:www-data storage bootstrap/cache
chmod -R 775 storage bootstrap/cache
exec supervisord -c /etc/supervisord.conf

Не забудьте chmod +x crater/entrypoint.sh на хосте.

docker-compose.yml целиком

В ~/crater-app/docker-compose.yml:

services:
  crater:
    build:
      context: ./crater
    container_name: crater
    restart: unless-stopped
    ports:
      - "8090:80"
    volumes:
      - ./crater.env:/var/www/html/.env
      - crater_storage:/var/www/html/storage
    depends_on:
      - db
    networks:
      - crater_net

  db:
    image: mysql:8.0
    container_name: crater_db
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: change_me_root_password
      MYSQL_DATABASE: crater
      MYSQL_USER: crater
      MYSQL_PASSWORD: change_me_db_password
    volumes:
      - crater_db_data:/var/lib/mysql
    networks:
      - crater_net

volumes:
  crater_storage:
  crater_db_data:

networks:
  crater_net:
    driver: bridge

Файл .env монтируется отдельным bind-volume, а не пишется в образ — так он переживёт пересборку контейнера. Перед первым запуском создайте пустой файл и откройте права на запись для пользователя контейнера (в php-alpine www-data обычно имеет uid 82):

touch ~/crater-app/crater.env
chmod 666 ~/crater-app/crater.env

После того как мастер установки заполнит файл, можно ужесточить права до chmod 640 и владельца 82:82 — просто не забудьте, что после этого повторная сборка образа с нуля правки не тронет: файл живёт вне образа.

Замените change_me_root_password и change_me_db_password на реальные пароли (openssl rand -base64 24) до первого docker compose up, а не после — MySQL инициализирует базу и учётные записи только при первом старте с пустым volume.

Первый запуск: мастер установки и переменные окружения

Запускаем сборку и старт:

cd ~/crater-app
docker compose up -d --build
docker compose logs -f crater

Первая сборка образа (composer + npm) обычно занимает несколько минут — точное время зависит от сервера и сети, ориентируйтесь на 3-10 минут на 2 vCPU. Дождитесь в логах записи о старте nginx и php-fpm от supervisord, затем откройте http://IP-сервера:8090 в браузере.

Crater показывает веб-мастер установки в несколько шагов:

  1. проверка требований окружения (расширения PHP, права на запись в storage/ и .env);
  2. параметры подключения к базе — здесь укажите db как хост (имя сервиса из docker-compose.yml, не localhost), порт 3306, имя базы crater, логин и пароль, которые вы прописали в переменных окружения контейнера db;
  3. создание учётной записи администратора (email и пароль);
  4. данные компании — название, адрес, валюта по умолчанию, логотип.

По завершении мастер сам пишет конфигурацию в смонтированный .env и выполняет миграции базы. Ошибка доступа на шаге подключения к БД почти всегда значит, что контейнер db ещё не успел завершить инициализацию — подождите полминуты, обновите страницу или проверьте docker compose logs db.

Держать инвойсы за портом :8090 без домена и HTTPS нормально только для тестового прогона — дальше нужен обратный прокси.

Домен, HTTPS и бэкапы

Направьте A-запись домена на IP сервера и поставьте перед Crater реверс-прокси с автоматическим SSL. Проще всего — Caddy, конфиг занимает три строки:

invoices.example.com {
    reverse_proxy localhost:8090
}

Подробности установки и типовые грабли (незавершившийся challenge, порт 80 занят) — в статье про Caddy с авто-SSL на Ubuntu 24.04. После того как прокси поднят, обновите APP_URL в crater.env на https://invoices.example.com — иначе ссылки на инвойсы в письмах клиентам будут генерироваться с внутренним IP и портом.

Бэкап — это две сущности: база данных и volume crater_storage (там лежат сгенерированные PDF, логотип компании, вложения). Дамп базы:

docker compose exec db mysqldump -u crater -p crater > crater_$(date +%F).sql

и архив volume:

docker run --rm -v crater-app_crater_storage:/data -v $(pwd):/backup alpine \
  tar czf /backup/crater_storage_$(date +%F).tar.gz -C /data .

Общие принципы регулярного и надёжного бэкапа MySQL-баз на сервере — с ротацией, проверкой дампа и типовыми ошибками — разобраны в статье про бэкап MySQL на VPS; имеет смысл поставить дамп и архив storage в один cron-скрипт с выгрузкой на внешнее хранилище, а не оставлять их рядом с самим сервером.

Обновление до новой версии — это пересборка образа с чистым клоном репозитория:

cd ~/crater-app
docker compose build --no-cache crater
docker compose up -d crater

Перед этим обязательно сделайте дамп базы: миграции применяются автоматически при старте нового кода, откат назад без бэкапа невозможен.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Crater поддерживает приём онлайн-оплаты картой?

Нет встроенного эквайринга — только ручная фиксация факта оплаты (наличные, перевод, что угодно). Это осознанное упрощение: если нужен приём платежей прямо на странице инвойса, смотрите в сторону Invoice Ninja или отдельного платёжного сервиса со ссылкой на оплату.

Можно ли использовать SQLite вместо MySQL, чтобы не поднимать второй контейнер?

Собранный в этой статье образ и compose-файл рассчитаны на MySQL — для SQLite потребуется другой Dockerfile (без сервиса db, с volume под файл базы) и проверка, что ваша версия Crater официально поддерживает такой драйвер.

После docker compose build --no-cache пропали кастомные PDF-шаблоны инвойсов.

Если шаблоны хранятся в storage/ — они сохранятся, volume не пересобирается вместе с образом. Если вы правили файлы в resources/ внутри контейнера — эти правки живут в образе и теряются при пересборке; храните такие кастомизации в отдельном Dockerfile-слое или патче.

Мастер установки не открывается, белый экран или 502.

Чаще всего причина в правах на .env (контейнер не может в него писать) или в том, что миграции упали из-за недоступной базы — проверьте docker compose logs crater и docker compose logs db по отдельности, ошибка обычно там же.

Нужен ли Redis для очередей и кэша?

Для одной небольшой компании и малого потока инвойсов файловый и database-драйверы Laravel справляются без проблем. Redis имеет смысл добавлять, когда счета генерируются массово и очереди начинают заметно тормозить интерфейс — тогда это отдельный сервис в том же docker-compose.yml.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →