DSpace 9 у Docker: самодостатній стек однією командою

Docker 4 лип. 2026 р.
 DSpace 9 у Docker: самодостатній стек однією командою

Інституційний репозиторій — обов'язковий атрибут сучасного університету чи наукової установи. Найпопулярніша платформа для цього — DSpace, і з виходом 7-ї, а тепер і 9-ї версії її архітектура кардинально змінилась: тепер це REST-бекенд на Java та окремий Angular-фронтенд із серверним рендерингом (SSR). Встановлювати все це вручну — заняття на кілька днів із граблями на кожному кроці: PostgreSQL із pgcrypto, Solr із специфічними cores, Ant, Maven, Node.js...

Я зібрав власний самодостатній Docker-стек для DSpace 9 і виклав його на GitHub: PechenkiUA/dspace-9-docker. Одна команда docker compose up -d — і за пів години у вас працює повноцінний репозиторій з Angular UI, REST API, Solr та PostgreSQL. У цій статті розповім, як він влаштований і які проблеми довелося розв'язати.

Чим цей стек відрізняється від офіційного

DSpace має офіційні Docker-образи, але вони орієнтовані передусім на розробку самого DSpace. Мій стек робить кілька речей інакше:

  • Збірка з сирців. Backend збирається Maven'ом із конкретної версії (задається в .env), тож можна накладати власні патчі та кастомізації ще на етапі збірки.
  • Єдина точка входу. Nginx на порту 80 маршрутизує /server на REST API, а решту — на Angular SSR. Назовні дивиться один порт.
  • Повністю автоматична ініціалізація. Перший запуск сам генерує local.cfg, виконує ant fresh_install, міграції бази та створює адміністратора — без жодної ручної команди.
  • Розв'язана проблема SSR + REST URL — про неї детально нижче, бо саме на ній ламається більшість Docker-розгортань DSpace.

Архітектура

Browser / curl
     │
     ▼
┌──────────────────────────────────────────────┐
│  nginx :80  (єдина точка входу)              │
│   ├── /server  ──►  DSpace REST API  :8080   │
│   └── /        ──►  Angular SSR      :4000   │
└──────────────────────────────────────────────┘
          │                      │
          ▼                      ▼
   PostgreSQL 16          Apache Solr 9.7

П'ять сервісів у docker-compose:

  • nginx (nginx:alpine) — reverse proxy;
  • dspace — REST API, кастомний образ на Eclipse Temurin 17 JRE;
  • dspace-ui — Angular SSR, кастомний образ на Node.js 20;
  • db — postgres:16-alpine із розширенням pgcrypto;
  • solr — Solr 9.7 із DSpace-конфігураціями cores.

Головна проблема DSpace у Docker — і як її розв'язано

Класична пастка, у яку потрапляють усі, хто загортає DSpace 7+ у Docker: Angular SSR виконується на сервері (у Node.js), але той самий код потім виконується у браузері користувача. І обидва мають звертатись до REST API за однаковим URL — інакше отримуємо або зламаний SSR, або зламану гідратацію на клієнті.

Типові «рішення» — прописувати host.docker.internal, правити hosts-файл усередині контейнера чи городити extra_hosts. Усе це крихко й не переноситься між середовищами.

У моєму стеку застосований прийом із Kubernetes: контейнер dspace-ui запускається з network_mode: "service:nginx" — тобто ділить мережевий простір із nginx, як контейнери в одному поді. У результаті:

  • SSR у Node.js викликає http://localhost/server → запит потрапляє в nginx (він на тому ж localhost) → далі на backend;
  • браузер користувача викликає той самий http://localhost/server → той самий nginx на порту 80 → backend.

Однаковий URL для сервера й клієнта, жодних DNS-хаків. Побічний ефект: nginx і dspace-ui треба перезапускати разом (docker compose restart nginx dspace-ui), бо вони живуть в одному network namespace.

І ще один нюанс, який коштував мені кількох годин дебагу: Node.js 18+ за замовчуванням біндиться на ::1 (IPv6 loopback), а не на 127.0.0.1. Тому в nginx.conf апстрім для Angular прописаний як [::1]:4000 — із 127.0.0.1:4000 ви отримаєте вічний 502.

Швидкий старт

git clone https://github.com/PechenkiUA/dspace-9-docker.git
cd dspace-9-docker

Мінімальні правки у .env:

# Версія DSpace
DSPACE_VERSION=9.0

# Публічні URL
DSPACE_SERVER_URL=http://localhost/server
DSPACE_UI_URL=http://localhost

# Назва репозиторію
DSPACE_NAME=My DSpace Repository

# Адміністратор (створюється автоматично)
[email protected]
DSPACE_ADMIN_PASS=changeme

# JVM
JAVA_OPTS=-Xmx2g -Xms512m -Dfile.encoding=UTF-8

Запуск:

docker compose up -d
docker compose logs -f dspace

Перший запуск триває 15–30 хвилин — Maven завантажує близько 500 МБ залежностей і збирає DSpace, потім Ant виконує fresh_install. Наступні запуски — лічені секунди: збірка кешується через BuildKit cache mount для .m2.

Коли все піднялося:

  • http://localhost — Angular UI;
  • http://localhost/server — REST API (HAL Browser);
  • http://localhost:8983/solr — Solr Admin (debug-порт).

Що відбувається під капотом при першому старті

Уся магія — в entrypoint-скрипті бекенда:

1. Генерується /dspace/config/local.cfg з env-змінних
2. ant fresh_install — розкладає файли, підключається до БД
3. dspace database migrate — Flyway-міграції схеми
4. dspace create-administrator — створює адмін-акаунт
5. java -jar server-boot.jar — стартує Spring Boot

Щоб кроки 2–4 не виконувались при кожному рестарті, у persistent volume створюється файл-маркер .initialized. Побачив маркер — одразу переходимо до запуску сервера. Простий прийом, який варто взяти на озброєння для будь-яких «важких» ініціалізацій у Docker.

Корисні команди

# Логи окремих сервісів
docker compose logs -f dspace
docker compose logs -f dspace-ui

# DSpace CLI всередині контейнера
docker exec -it dspace-backend bash
/dspace/bin/dspace index-discovery -b   # переіндексація Solr
/dspace/bin/dspace database migrate     # міграції БД

# Перезбірка після зміни конфігів збірки
docker compose up -d --build dspace

# Повне скидання (видаляє ВСІ дані!)
docker compose down -v

Що зробити перед виходом у продакшн

  • замінити всі паролі в .env (особливо DSPACE_ADMIN_PASS та DB_PASS);
  • налаштувати HTTPS — Certbot поверх nginx або зовнішній балансувальник; як варіант — Cloudflare Tunnel, якщо сервер без білого IP;
  • закрити debug-порти 8080 (backend) та 8983 (Solr) у compose-файлі;
  • збільшити JAVA_OPTS для великих колекцій (-Xmx4g -Xms1g);
  • налаштувати SMTP (mail.* у local.cfg) — без нього не працюватимуть реєстрація та сповіщення;
  • поставити на розклад бекапи volumes pgdata (база) та dspace_home (assetstore з файлами документів). Solr бекапити не потрібно — індекс відновлюється командою index-discovery -b.

Типові проблеми

502 Bad Gateway після перезапуску nginx. Nginx і dspace-ui ділять network namespace, тому перезапускати їх треба разом: docker compose restart nginx dspace-ui.

Перший старт «завис». Це нормально: Maven тягне ~500 МБ залежностей. Дивіться прогрес у docker compose logs -f dspace.

local.cfg: No such file or directory. У volume залишився маркер від невдалої попередньої установки. Повне скидання: docker compose down -v && docker compose up -d.

Solr healthcheck не проходить. Дайте йому 2–3 хвилини після першого старту — cores ініціалізуються повільно.

Висновок

Docker перетворює розгортання DSpace 9 з багатоденного квесту на одну команду. Ключові рішення цього стеку — спільний network namespace для nginx та Angular SSR (однаковий URL API для сервера й браузера без DNS-хаків), автоматична ініціалізація через entrypoint із маркером .initialized та збірка з сирців, яка дозволяє кастомізувати DSpace під себе.

Репозиторій відкритий: github.com/PechenkiUA/dspace-9-docker — issues та pull requests вітаються. У наступних матеріалах розглянемо міграцію контенту зі старих версій DSpace (5/7) через SAF-пакети та публікацію репозиторію через Cloudflare Tunnel без білого IP.