Using Docker Compose as Backend for Local Development
Part 1 — SoVisu+ development
This section explains how to use the Docker Compose stack strictly as a backend while running SoVisu+ locally (Next.js dev server) for frontend development.
🎯 Goal
Run all shared services (Neo4j, message bus, Keycloak, APIs, databases) in Docker, but run SoVisu+ on your host machine. To make this work, you must:
- Map the necessary service ports from containers to the host.
- Use a dedicated profile (e.g.
sovisuplus-db) to start only SoVisu+ backend services. - Point your local SoVisu+ to these services via env vars and
/etc/hosts.
🧱 Required Hostnames
Add these entries to your /etc/hosts (if not already done in the main guide):
127.0.0.1 sovisuplus.local
127.0.0.1 keycloak.localSoVisu+ uses ORCID OAuth, which requires valid hostnames even for sandbox keys.
🔓 Open the Right Ports in Compose
You’ll run SoVisu+ locally, so your host needs to reach the containers. Make sure the following port mappings are enabled.
1) SoVisu+ database (docker/sovisuplus/sovisuplus.yaml)
Uncomment the ports section so Postgres is reachable from your host:
sovisuplus.yaml
services:
svp-db:
# Overriden by environment specific YAML configurations (e.g. docker-compose.dev.yaml)
# image: postgres:16
restart: unless-stopped
environment:
POSTGRES_USER: ${SVP_DB_USER}
POSTGRES_PASSWORD: ${SVP_DB_PASSWORD}
POSTGRES_DB: ${SVP_DB_NAME}
expose:
- 5432
ports:
- 5432:5432
volumes:
- ./postgres-data:/var/lib/postgresql/data
networks:
- svp-network
healthcheck:
test: [ 'CMD-SHELL', 'pg_isready -d ${SVP_DB_NAME} -U ${SVP_DB_USER}' ]
interval: 1s
timeout: 5s
retries: 10
command: [ "postgres","-c","max_connections=400","-c","superuser_reserved_connections=3" ]
profiles:
- sovisuplus-db
sovisuplus:
# Overriden by environment specific YAML configurations (e.g. docker-compose.dev.yaml)
# image: crisalidesr/sovisuplus:latest
restart: unless-stopped
ports:
- ${SVP_PORT:-3000}:3000
- ${SVP_WS_INTERNAL_PORT:-3001}:3001
environment:
- INIT_ROLES_ON_START=true
- RBAC_ROLES_FILE=/config/rbac.roles.yaml
- DB_HOST=${SVP_DB_HOST:-svp-db}
- DB_NAME=${SVP_DB_NAME}
- DB_USER=${SVP_DB_USER}
- DB_PASSWORD=${SVP_DB_PASSWORD}
- DB_PORT=${SVP_DB_PORT:-5432}
- APP_URL=${SOVISUPLUS_URL}
- NEXT_PUBLIC_BASE_URL=${SOVISUPLUS_URL}
- NEXT_PUBLIC_WS_SCHEME=${SVP_WS_SCHEME}
- NEXT_PUBLIC_WS_HOST=${SVP_WS_HOST}
- NEXT_PUBLIC_WS_PORT=${SVP_WS_PORT}
- NEXT_PUBLIC_WS_PATH=${SVP_WS_PATH}
- NEXTAUTH_URL=${SOVISUPLUS_URL}/api/auth
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
- JWT_TOKEN_EXPIRATION_HOURS=${JWT_TOKEN_EXPIRATION_HOURS:-12}
- AMQP_HOST=${CRISALID_BUS_HOST:-crisalid-bus}
- AMQP_PORT=${CRISALID_BUS_AMQP_PORT}
- AMQP_USER=${CRISALID_BUS_USER}
- AMQP_PASSWORD=${CRISALID_BUS_PASSWORD}
- AMQP_INTERACTIVE_QUEUE_NAME=${SVP_AMQP_INTERACTIVE_QUEUE_NAME:-sovisuplus-interactive}
- AMQP_BATCH_QUEUE_NAME=${SVP_AMQP_BATCH_QUEUE_NAME:-sovisuplus-batch}
- AMQP_EXCHANGE_NAME=${SVP_AMQP_EXCHANGE_NAME}
- GRAPHQL_ENDPOINT_ENABLED=${GRAPHQL_ENDPOINT_ENABLED}
- GRAPHQL_ENDPOINT_URL=http://${APOLLO_HOST:-apollo}:${APOLLO_API_PORT}/graphql
- GRAPHQL_API_KEY_ENABLED=${APOLLO_ENABLE_API_KEYS}
- GRAPHQL_API_KEY=${SOVISUPLUS_GRAPHQL_API_KEY}
# Kill switch for the AI chat assistant: set SVP_CHAT_ENABLED=false to hide it (default: enabled)
- CHAT_ENABLED=${SVP_CHAT_ENABLED:-true}
# CRISalid agents chat API (server-to-server over crisalid-front)
- CRISALID_AGENTS_API_URL=${SVP_CRISALID_CHAT_API_URL:-http://crisalid-agents-chat-api:9100}
- CRISALID_AGENTS_API_KEY=${SOVISUPLUS_CHAT_API_KEY}
# Agent to talk to; must be one of the agents served by crisalid-agents (CRISALID_AGENTS_AGENTS)
- CRISALID_AGENTS_AGENT=${SVP_CRISALID_AGENTS_AGENT:-generic_agent}
- CHAT_CONFIG_FILE=/config/chat.json
- KEYCLOAK_CLIENT_ID=${SOVISUPLUS_KEYCLOAK_CLIENT_ID}
- KEYCLOAK_CLIENT_SECRET=${SOVISUPLUS_KEYCLOAK_CLIENT_SECRET}
- KEYCLOAK_PUBLIC_ADDR=${KEYCLOAK_SCHEME}://${KEYCLOAK_HOST}:${KEYCLOAK_PORT}
- KEYCLOAK_INTERNAL_ADDR=http://${KEYCLOAK_INTERNAL_HOST}:8080
- KEYCLOAK_REALM=${KEYCLOAK_REALM}
- ORCID_URL=${ORCID_URL}
- ORCID_SCOPES=${ORCID_SCOPES}
- ORCID_CLIENT_ID=${ORCID_CLIENT_ID}
- ORCID_CLIENT_SECRET=${ORCID_CLIENT_SECRET}
- VOCABS_URL=${CRISALID_VOCAB_SEARCH_URL}
- NEXT_PUBLIC_AVAILABLE_VOCABS=${CRISALID_VOCAB_SEARCH_AVAILABLE_VOCABS:-jel,aat,acm,elsst,pactols,euroscivoc}
- PERSPECTIVE_ROLES_FILTER=${PERSPECTIVE_ROLES_FILTER}
- PUBLICATION_LIST_ROLES_FILTER=${PUBLICATION_LIST_ROLES_FILTER}
- DEFAULT_SELF_SCOPED_ROLES=${DEFAULT_SELF_SCOPED_ROLES}
- NEXT_PUBLIC_INSTITUTION_NAME=${INSTITUTION_NAME}
- FIELD_ENC_PRIMARY_KID=${FIELD_ENC_PRIMARY_KID}
- FIELD_ENC_KEYS_JSON=${FIELD_ENC_KEYS_JSON}
- NEXT_PUBLIC_WARN_MISSING_IDENTIFIER_TYPES=${SVP_WARN_MISSING_IDENTIFIER_TYPES}
- NEXT_PUBLIC_SUPPORT_SERVICE_EMAIL_ADDR=${SVP_MAINTENANCE_MAIL_ADDRESS}
- NEXT_PUBLIC_ABOUT_PAGE_URL=${SVP_ABOUT_PAGE_URL}
- NEXT_PUBLIC_TERMS_PAGE_URL=${SVP_TERMS_PAGE_URL}
- NEXT_PUBLIC_COMMUNITY_PAGE_URL=${SVP_COMMUNITY_PAGE_URL}
- NEXT_PUBLIC_HELP_URL=${SVP_HELP_URL}
# Help button: 'url' -> SVP_HELP_URL, 'mail' -> SVP_MAINTENANCE_MAIL_ADDRESS, unset -> hidden
- NEXT_PUBLIC_HELP_LINK_TYPE=${SVP_HELP_LINK_TYPE}
# HAL deposit (server-side). Files are written under HAL_UPLOADS_ROOT, mounted below.
- HAL_ENDPOINT=${SVP_HAL_ENDPOINT:-https://api-preprod.archives-ouvertes.fr}
- HAL_SERVICE_ACCOUNT_LOGIN=${SVP_HAL_SERVICE_ACCOUNT_LOGIN}
- HAL_SERVICE_ACCOUNT_PASSWORD=${SVP_HAL_SERVICE_ACCOUNT_PASSWORD}
- HAL_UPLOADS_ROOT=/app/uploads
depends_on:
crisalid-bus:
condition: service_healthy
volumes:
- ./config:/config:ro
- ./theme:/custom-theme:ro
# HAL deposit files: uploaded attachments (hal-files) + generated TEI/ZIP (hal-tei).
# Named volume so Docker seeds it from the image's /app/uploads (owned by the
# non-root nextjs user); a bind mount would be auto-created as root and break mkdir.
- svp-uploads:/app/uploads
networks:
- svp-network
- crisalid-front
profiles:
- sovisuplus
networks:
svp-network:
driver: bridge
crisalid-front:
driver: bridge
volumes:
svp-uploads:services:
svp-db:
image: postgres:16
container_name: svp-db
restart: unless-stopped
environment:
POSTGRES_USER: ${SVP_DB_USER}
POSTGRES_PASSWORD: ${SVP_DB_PASSWORD}
POSTGRES_DB: ${SVP_DB_NAME}
expose:
- 5432
ports:
- 5432:54322) Apollo API (docker/apollo/apollo.yaml)
Ensure the public mapping is present (it typically is already because Apollo graphql GUI is one of the main user interfaces):
apollo.yaml
services:
apollo:
# Overriden by environment specific YAML configurations (e.g. docker-compose.dev.yaml)
# image: crisalidesr/crisalid-apollo:latest
restart: unless-stopped
ports:
- ${APOLLO_API_PORT}:4000
depends_on:
neo4j:
condition: service_healthy
environment:
# Overriden by environment specific YAML configurations (e.g. docker-compose.dev.yaml)
# - APP_ENV=DEV
- NEO4J_URI=bolt://${NEO4J_HOST}:${NEO4J_BOLT_PORT}
- NEO4J_USER=${NEO4J_USER}
- NEO4J_PASSWORD=${NEO4J_PASSWORD}
- ENABLE_API_KEYS=${APOLLO_ENABLE_API_KEYS}
- API_KEYS=${SOVISUPLUS_GRAPHQL_API_KEY}
networks:
- ikg-network
- crisalid-front
profiles:
- apollo
networks:
ikg-network:
driver: bridge
crisalid-front:
driver: bridgeservices:
apollo:
image: crisalidesr/crisalid-apollo:latest
ports:
- ${APOLLO_API_PORT}:4000Your local SoVisu+ will call
http://localhost:${APOLLO_API_PORT}(GraphQL endpoint/graphql).
3) CRISalid Bus / RabbitMQ (docker/crisalid-bus/crisalid-bus.yaml)
Only the management UI port is exposed by default, but you can uncomment the AMQP port to allow external tools to connect:
crisalid-bus.yaml
services:
crisalid-bus:
# Overriden by environment specific YAML configurations (e.g. docker-compose.dev.yaml)
# image: rabbitmq:3-management
restart: unless-stopped
environment:
- RABBITMQ_DEFAULT_USER=${CRISALID_BUS_USER}
- RABBITMQ_DEFAULT_PASS=${CRISALID_BUS_PASSWORD}
- RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS=-rabbit load_definitions "${CRISALID_BUS_DEFINITIONS_FILE}"
ports:
- "${CRISALID_BUS_HTTP_PORT}:15672"
# - "${CRISALID_BUS_AMQP_PORT}:5672" # To connect app under development on host machine or from external server
expose:
- "${CRISALID_BUS_AMQP_PORT}"
volumes:
- ./rabbitmq-data:/var/lib/rabbitmq
- ./rabbitmq-logs/:/var/log/rabbitmq
- ./definitions.json:${CRISALID_BUS_DEFINITIONS_FILE}:ro
healthcheck:
test: rabbitmq-diagnostics check_port_connectivity
interval: 1s
timeout: 3s
retries: 30
networks:
- crisalid-front
- crisalid-back
profiles:
- crisalid-bus
networks:
crisalid-front:
driver: bridge
crisalid-back:
driver: bridgeservices:
crisalid-bus:
image: rabbitmq:3-management
container_name: 'crisalid-bus'
environment:
- RABBITMQ_DEFAULT_USER=${CRISALID_BUS_USER}
- RABBITMQ_DEFAULT_PASS=${CRISALID_BUS_PASSWORD}
- RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS=-rabbit load_definitions "${CRISALID_BUS_DEFINITIONS_FILE}"
ports:
- "${CRISALID_BUS_HTTP_PORT}:15672"
- "${CRISALID_BUS_AMQP_PORT}:5672"
expose:
- "${CRISALID_BUS_AMQP_PORT}"🧩 Use the Backend-Only Profile
Replace the sovisuplus profile with sovisuplus-db, which starts the DB and related services but not the SoVisu+ container itself.
Example command:
docker compose \
--profile cdb \
--profile neo4j \
--profile apollo \
--profile crisalid-bus \
--profile harvester \
--profile ikg \
--profile keycloak \
--profile sovisuplus-db \
up --remove-orphans▶️ Run SoVisu+ Locally
Start your Next.js app on the host as usual (e.g., in the SoVisu+ repo):
npm run dev # for the main web gui
npm run dev:listener # for the backend listenerMake sure your local env points to the Docker services. Typical variables (names vary by project):
NEXT_PUBLIC_SUPPORTED_LOCALES="fr,en"
DATABASE_URL="postgresql://sovisuplus:sovisuplus_password@localhost:5432/sovisuplus?schema=public"
KEYCLOAK_CLIENT_ID="sovisuplus"
KEYCLOAK_CLIENT_SECRET="use-the-same-secret-as-in-docker-compose"
KEYCLOAK_ISSUER="http://keycloak.local:8080/realms/crisalid-inst"
NEXTAUTH_SECRET="use-a-secure-random-secret"
AMQP_USER="crisalid_bus_user"
AMQP_PASSWORD="use-the-same-password-as-in-docker-compose"
AMQP_HOST="localhost"
AMQP_PORT="5672"
AMQP_QUEUE_NAME="sovisuplus"
AMQP_EXCHANGE_NAME="graph"
GRAPHQL_ENDPOINT_ENABLED="true"
GRAPHQL_ENDPOINT_URL="http://localhost:4000/graphql"
GRAPHQL_API_KEY_ENABLED="false"
GRAPHQL_API_KEY="not-needed-in-dev"
PERSPECTIVE_ROLES_FILTER=["author ","author of introduction, etc. ","author of afterword, colophon, etc. ","author in quotations or text abstracts ","editor ","editor of compilation ","translator" ]
PUBLICATION_LIST_ROLES_FILTER=["analyst ","annotator ","author ","author in quotations or text abstracts ","author of afterword, colophon, etc. ","author of introduction, etc. ","cartographer ","commentator for written text ","compiler ","composer ","conceptor ","contributor ","curator ","degree committee member ","dissertant ","donor ","editor ","editor of compilation ","film director ","film editor ","former owner ","illustrator ","interviewee ","interviewer ","opponent ","organizer ","other ","photographer ","praeses ","production personnel ","project director ","publisher director ","rapporteur ","scientific advisor ","software developer ","sound designer ","speaker ","stage manager ","thesis advisor ","translator ","writer of accompanying material"]
ORCID_URL="https://sandbox.orcid.org"
APP_URL="http://sovisuplus.local:3000"
ORCID_SCOPES="/person/update"
ORCID_CLIENT_ID="use-the-same-client-id--provided-by-orcid-as-in-docker-compose"
ORCID_CLIENT_SECRET="use-the-same-client-secret-provided-by-orcid-as-in-docker-compose"Visit SoVisu+ at:
http://sovisuplus.local:3000