From 0e43e0f91e90afbf8360838942445f1c060319bc Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Fri, 11 Sep 2026 15:48:56 +0200 Subject: [PATCH 01/30] Dockerfile: better multi-layer caching, return to gunicorn --- .dockerignore | 19 +++++++++++++++++++ Dockerfile | 28 ++++++++++++++++++++++++---- Dockerfile.dev | 37 ++++++++++++++++++++++++------------- pyproject.toml | 1 + uv.lock | 23 +++++++++++++++++------ 5 files changed, 85 insertions(+), 23 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..0dbf8007 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,19 @@ +.git +.github +.gitattributes +.gitignore +.gitlab-ci.yml +.venv +.pytest_cache +.coverage +**/__pycache__ +**/*.pyc +*.egg-info +*.log +upload_folder/ +docs/ +tests/ +ci/ +Dockerfile +Dockerfile.dev +docker-compose*.yml diff --git a/Dockerfile b/Dockerfile index 7f7aeb3f..f45d2500 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,10 @@ ARG PYVER=3.12 FROM ghcr.io/astral-sh/uv:python${PYVER}-trixie-slim -ENV UV_NO_DEV=1 -ENV SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0 +ENV UV_NO_DEV=1 \ + UV_LINK_MODE=copy \ + UV_COMPILE_BYTECODE=1 \ + PYTHONUNBUFFERED=1 WORKDIR /app @@ -14,11 +16,29 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libmagic1 \ && rm -rf /var/lib/apt/lists/* -COPY uv.lock pyproject.toml alembic.ini ./ +# Install dependencies in their own layer, cached independently. +COPY uv.lock pyproject.toml ./ +RUN uv sync --locked --no-install-project --extra all + +ARG APP_VERSION=0.0.0 + +LABEL org.opencontainers.image.title="SimDB" \ + org.opencontainers.image.description="ITER Simulation Management Tool" \ + org.opencontainers.image.source="https://github.com/iterorganization/SimDB" \ + org.opencontainers.image.licenses="LGPL-3.0-only" \ + org.opencontainers.image.version="${APP_VERSION}" \ + io.simdb.component="server" + +# Add the project source and finish the sync. +ENV SETUPTOOLS_SCM_PRETEND_VERSION="${APP_VERSION}" +COPY alembic.ini ./ COPY src/ ./src/ RUN uv sync --locked --extra all ENV SIMDB_SITE_CONFIG_PATH=/app/config/simdb.cfg -CMD ["uv", "run", "simdb_server"] +EXPOSE 5000 + +# Run under Gunicorn rather than the Werkzeug dev server +CMD ["uv", "run", "gunicorn", "--bind=0.0.0.0:5000", "--workers=3", "simdb.remote.wsgi:app"] diff --git a/Dockerfile.dev b/Dockerfile.dev index 4b37d3fd..0cbdb8ae 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,6 +1,26 @@ ARG PYVER=3.12 FROM ghcr.io/astral-sh/uv:python${PYVER}-trixie-slim +ENV UV_NO_DEV=1 \ + UV_LINK_MODE=copy \ + PYTHONUNBUFFERED=1 \ + SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0 + +WORKDIR /app + +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + libpq-dev \ + libldap2-dev \ + libsasl2-dev \ + libmagic1 \ + && rm -rf /var/lib/apt/lists/* + +# Install dependencies in their own layer, cached independently. +COPY uv.lock pyproject.toml ./ +RUN uv sync --locked --no-install-project --no-build --extra all + +# Add the project source and finish the sync. ARG SIMDB_GIT_BRANCH=unknown ARG SIMDB_GIT_COMMIT=unknown ARG SIMDB_DEPLOYMENT=local @@ -9,26 +29,17 @@ LABEL org.simdb.deployment="${SIMDB_DEPLOYMENT}" \ org.simdb.git.branch="${SIMDB_GIT_BRANCH}" \ org.simdb.git.commit="${SIMDB_GIT_COMMIT}" -ENV UV_NO_DEV=1 -ENV SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0 + ENV SIMDB_DEPLOYMENT="${SIMDB_DEPLOYMENT}" ENV SIMDB_GIT_BRANCH="${SIMDB_GIT_BRANCH}" ENV SIMDB_GIT_COMMIT="${SIMDB_GIT_COMMIT}" -WORKDIR /app - -RUN apt-get update && apt-get install -y --no-install-recommends \ - build-essential \ - libpq-dev \ - libldap2-dev \ - libsasl2-dev \ - libmagic1 \ - && rm -rf /var/lib/apt/lists/* - -COPY uv.lock pyproject.toml alembic.ini ./ +COPY alembic.ini ./ COPY src/ ./src/ RUN uv sync --locked --extra all ENV SIMDB_SITE_CONFIG_PATH=/app/config/simdb.cfg +EXPOSE 5000 + CMD ["uv", "run", "simdb_server"] diff --git a/pyproject.toml b/pyproject.toml index 6de7bc86..abe1195e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,6 +50,7 @@ dependencies = [ "sqlalchemy>=1.2.12,<2.0", "alembic~=1.13", "rich>=14.3.3", + "gunicorn>=26.2.0", ] [project.optional-dependencies] diff --git a/uv.lock b/uv.lock index 4130318a..d226fa87 100644 --- a/uv.lock +++ b/uv.lock @@ -3,10 +3,10 @@ revision = 3 requires-python = ">=3.11" resolution-markers = [ "python_full_version >= '3.13' and platform_machine == 'ARM64' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and platform_machine == 'ARM64' and sys_platform == 'win32'", - "python_full_version < '3.12' and platform_machine == 'ARM64' and sys_platform == 'win32'", "(python_full_version >= '3.13' and platform_machine != 'ARM64') or (python_full_version >= '3.13' and sys_platform != 'win32')", + "python_full_version == '3.12.*' and platform_machine == 'ARM64' and sys_platform == 'win32'", "(python_full_version == '3.12.*' and platform_machine != 'ARM64') or (python_full_version == '3.12.*' and sys_platform != 'win32')", + "python_full_version < '3.12' and platform_machine == 'ARM64' and sys_platform == 'win32'", "(python_full_version < '3.12' and platform_machine != 'ARM64') or (python_full_version < '3.12' and sys_platform != 'win32')", ] @@ -982,6 +982,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/93/e8/65e8707d00fe2a49bf12f609a9b2b39ba6dd23c2810eacad877c4fc94bfe/greenlet-3.5.4-cp315-cp315t-win_arm64.whl", hash = "sha256:08fc36de8442d5c3e95b044550dbea9bf144d31ec0cc58e36fb241cb6ef6a994", size = 250538, upload-time = "2026-07-22T11:40:17.985Z" }, ] +[[package]] +name = "gunicorn" +version = "26.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/8a/e4ef6ee11701b6cd64702848415ffb69eeff85cb388a3c6c7fe86f22f3f8/gunicorn-26.2.0.tar.gz", hash = "sha256:62b864895d9ebff0b2f9867ba04fe811c93121596540830c9c916d0769668447", size = 787921, upload-time = "2026-08-24T15:05:59.3Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/85/7522a52e5e2f42faf1a129113ab63e548c42e103e9af395b7bfe65e403e2/gunicorn-26.2.0-py3-none-any.whl", hash = "sha256:bd249d0b3f7972f7432f0a6b6ff3b3ee2d129f70cd1ff6c09a9dd9e29a2b88e3", size = 228389, upload-time = "2026-08-24T15:05:57.67Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -1161,6 +1170,7 @@ dependencies = [ { name = "click-option-group" }, { name = "distro" }, { name = "email-validator" }, + { name = "gunicorn" }, { name = "imas-python" }, { name = "netcdf4" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, @@ -1261,6 +1271,7 @@ requires-dist = [ { name = "flask-cors", marker = "extra == 'server'", specifier = ">=3" }, { name = "flask-mail", marker = "extra == 'server'", specifier = "~=0.9.1" }, { name = "flask-restx", marker = "extra == 'server'", specifier = ">=1.0.0" }, + { name = "gunicorn", specifier = ">=26.2.0" }, { name = "imas-python", specifier = ">=2.0.1" }, { name = "imas-simdb", extras = ["auth-ad", "auth-keycloak", "auth-ldap"], marker = "extra == 'auth'" }, { name = "imas-simdb", extras = ["imas-validator", "postgres", "server"], marker = "extra == 'all'" }, @@ -1679,8 +1690,8 @@ version = "2.5.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13' and platform_machine == 'ARM64' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and platform_machine == 'ARM64' and sys_platform == 'win32'", "(python_full_version >= '3.13' and platform_machine != 'ARM64') or (python_full_version >= '3.13' and sys_platform != 'win32')", + "python_full_version == '3.12.*' and platform_machine == 'ARM64' and sys_platform == 'win32'", "(python_full_version == '3.12.*' and platform_machine != 'ARM64') or (python_full_version == '3.12.*' and sys_platform != 'win32')", ] sdist = { url = "https://files.pythonhosted.org/packages/22/fd/89965aa4ac08c74998539fcbf24fa3540f3e15237fbeb6bcf9c908f4aade/numpy-2.5.1.tar.gz", hash = "sha256:a48a113e6afea91f5608793bafa7ef2ad481fefbda87ec5069f483de61cb9fa3", size = 20755553, upload-time = "2026-07-04T17:08:00.933Z" } @@ -2447,8 +2458,8 @@ version = "1.18.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13' and platform_machine == 'ARM64' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and platform_machine == 'ARM64' and sys_platform == 'win32'", "(python_full_version >= '3.13' and platform_machine != 'ARM64') or (python_full_version >= '3.13' and sys_platform != 'win32')", + "python_full_version == '3.12.*' and platform_machine == 'ARM64' and sys_platform == 'win32'", "(python_full_version == '3.12.*' and platform_machine != 'ARM64') or (python_full_version == '3.12.*' and sys_platform != 'win32')", ] dependencies = [ @@ -2611,8 +2622,8 @@ version = "9.1.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13' and platform_machine == 'ARM64' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and platform_machine == 'ARM64' and sys_platform == 'win32'", "(python_full_version >= '3.13' and platform_machine != 'ARM64') or (python_full_version >= '3.13' and sys_platform != 'win32')", + "python_full_version == '3.12.*' and platform_machine == 'ARM64' and sys_platform == 'win32'", "(python_full_version == '3.12.*' and platform_machine != 'ARM64') or (python_full_version == '3.12.*' and sys_platform != 'win32')", ] dependencies = [ @@ -2661,8 +2672,8 @@ version = "3.13.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13' and platform_machine == 'ARM64' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and platform_machine == 'ARM64' and sys_platform == 'win32'", "(python_full_version >= '3.13' and platform_machine != 'ARM64') or (python_full_version >= '3.13' and sys_platform != 'win32')", + "python_full_version == '3.12.*' and platform_machine == 'ARM64' and sys_platform == 'win32'", "(python_full_version == '3.12.*' and platform_machine != 'ARM64') or (python_full_version == '3.12.*' and sys_platform != 'win32')", ] dependencies = [ From 3f9597be01bb4bcdfdb0d32f84f21191452d5321 Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Fri, 11 Sep 2026 16:12:05 +0200 Subject: [PATCH 02/30] docker-compose: only need to build image once Add `image` spec so docker compose builds it only once instead four times for migrations, web, worker, and beat. --- docker-compose-dev.yml | 4 ++++ docker-compose.yml | 25 +++++++++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index e09d481d..6624394a 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -7,6 +7,7 @@ services: SIMDB_GIT_BRANCH: ${SIMDB_GIT_BRANCH:-unknown} SIMDB_GIT_COMMIT: ${SIMDB_GIT_COMMIT:-unknown} SIMDB_DEPLOYMENT: ${SIMDB_DEPLOYMENT:-local} + image: simdb-dev:${SIMDB_IMAGE_TAG:-local} volumes: - ./config:/app/config:ro environment: @@ -29,6 +30,7 @@ services: SIMDB_GIT_BRANCH: ${SIMDB_GIT_BRANCH:-unknown} SIMDB_GIT_COMMIT: ${SIMDB_GIT_COMMIT:-unknown} SIMDB_DEPLOYMENT: ${SIMDB_DEPLOYMENT:-local} + image: simdb-dev:${SIMDB_IMAGE_TAG:-local} environment: IMAS_LOCAL_HOSTS: localhost SIMDB_DEPLOYMENT: ${SIMDB_DEPLOYMENT:-local} @@ -105,6 +107,7 @@ services: SIMDB_GIT_BRANCH: ${SIMDB_GIT_BRANCH:-unknown} SIMDB_GIT_COMMIT: ${SIMDB_GIT_COMMIT:-unknown} SIMDB_DEPLOYMENT: ${SIMDB_DEPLOYMENT:-local} + image: simdb-dev:${SIMDB_IMAGE_TAG:-local} command: uv run simdb_worker environment: SIMDB_DEPLOYMENT: ${SIMDB_DEPLOYMENT:-local} @@ -134,6 +137,7 @@ services: SIMDB_GIT_BRANCH: ${SIMDB_GIT_BRANCH:-unknown} SIMDB_GIT_COMMIT: ${SIMDB_GIT_COMMIT:-unknown} SIMDB_DEPLOYMENT: ${SIMDB_DEPLOYMENT:-local} + image: simdb-dev:${SIMDB_IMAGE_TAG:-local} command: uv run simdb_beat environment: SIMDB_DEPLOYMENT: ${SIMDB_DEPLOYMENT:-local} diff --git a/docker-compose.yml b/docker-compose.yml index eff71ff3..f488fead 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,10 @@ services: migrations: - build: . + build: + context: . + args: + APP_VERSION: ${APP_VERSION:-0.0.0} + image: simdb:${SIMDB_IMAGE_TAG:-local} volumes: - ./config:/app/config:ro environment: @@ -11,7 +15,12 @@ services: restart: "no" web: - build: . + build: + context: . + args: + APP_VERSION: ${APP_VERSION:-0.0.0} + image: simdb:${SIMDB_IMAGE_TAG:-local} + container_name: ${SIMDB_CONTAINER_NAME:-simdb-web} environment: IMAS_LOCAL_HOSTS: localhost ports: @@ -33,7 +42,11 @@ services: worker: profiles: [with_workers] # docker compose --profile with_workers enables this service - build: . + build: + context: . + args: + APP_VERSION: ${APP_VERSION:-0.0.0} + image: simdb:${SIMDB_IMAGE_TAG:-local} command: uv run simdb_worker volumes: - ./config:/app/config:ro @@ -51,7 +64,11 @@ services: beat: profiles: [with_workers] # docker compose --profile with_workers enables this service - build: . + build: + context: . + args: + APP_VERSION: ${APP_VERSION:-0.0.0} + image: simdb:${SIMDB_IMAGE_TAG:-local} command: uv run simdb_beat volumes: - ./config:/app/config:ro From cd4970aac35a6bbbe01eb12c99efb310415cf2cc Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Tue, 15 Sep 2026 18:58:04 +0200 Subject: [PATCH 03/30] gunicorn: expose access and error logs to stdout --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f45d2500..0da4e814 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,5 +40,5 @@ ENV SIMDB_SITE_CONFIG_PATH=/app/config/simdb.cfg EXPOSE 5000 # Run under Gunicorn rather than the Werkzeug dev server -CMD ["uv", "run", "gunicorn", "--bind=0.0.0.0:5000", "--workers=3", "simdb.remote.wsgi:app"] +CMD ["uv", "run", "gunicorn", "--bind=0.0.0.0:5000", "--workers=3", "--access-logfile=-", "--error-logfile=-", "simdb.remote.wsgi:app"] From c2cbe1fb9330dfcbd15eace37b11dee3770e819c Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Wed, 16 Sep 2026 21:08:26 +0200 Subject: [PATCH 04/30] docker: uv sync --no-build for dependencies So that it fails quickly rather than try to (slowly) build a dependency when the built package proves unavailable. This was the case for flask-mail~=0.9.1 at python 3.12. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 0da4e814..85a31b51 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # Install dependencies in their own layer, cached independently. COPY uv.lock pyproject.toml ./ -RUN uv sync --locked --no-install-project --extra all +RUN uv sync --locked --no-install-project --no-build --extra all ARG APP_VERSION=0.0.0 From 51cad9ab712583b91e3c643799bd961a132296ed Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Wed, 16 Sep 2026 21:08:58 +0200 Subject: [PATCH 05/30] pyproject: bump flask-mail>=0.10.0 move gunicorn to server dependencies, which are sorted --- pyproject.toml | 8 ++++---- uv.lock | 14 +++++++++----- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index abe1195e..656ad3c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,21 +50,21 @@ dependencies = [ "sqlalchemy>=1.2.12,<2.0", "alembic~=1.13", "rich>=14.3.3", - "gunicorn>=26.2.0", ] [project.optional-dependencies] server = [ "Flask>=3.0", + "celery>=5.3.0", "flask-caching>=1.10", "flask-compress>=1.12", "flask-cors>=3", - "flask-mail~=0.9.1", + "flask-mail>=0.10.0", "flask-restx>=1.0.0", + "gunicorn>=26.2.0", "python-magic~=0.4", - "simplejson~=3.0", - "celery>=5.3.0", "redis>=5.0.0", + "simplejson~=3.0", ] auth-ad = [ "easyad>=1.0", diff --git a/uv.lock b/uv.lock index d226fa87..d6713178 100644 --- a/uv.lock +++ b/uv.lock @@ -894,13 +894,16 @@ wheels = [ [[package]] name = "flask-mail" -version = "0.9.1" +version = "0.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "blinker" }, { name = "flask" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/05/2f/6a545452040c2556559779db87148d2a85e78a26f90326647b51dc5e81e9/Flask-Mail-0.9.1.tar.gz", hash = "sha256:22e5eb9a940bf407bcf30410ecc3708f3c56cc44b29c34e1726fe85006935f41", size = 45654, upload-time = "2014-09-28T23:35:22.329Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/29/e92dc84c675d1e8d260d5768eb3fb65c70cbd33addecf424187587bee862/flask_mail-0.10.0.tar.gz", hash = "sha256:44083e7b02bbcce792209c06252f8569dd5a325a7aaa76afe7330422bd97881d", size = 8152, upload-time = "2024-05-23T22:30:12.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/c0/a81083da779f482494d49195d8b6c9fde21072558253e4a9fb2ec969c3c1/flask_mail-0.10.0-py3-none-any.whl", hash = "sha256:a451e490931bb3441d9b11ebab6812a16bfa81855792ae1bf9c1e1e22c4e51e7", size = 8529, upload-time = "2024-05-23T22:30:10.962Z" }, +] [[package]] name = "flask-restx" @@ -1170,7 +1173,6 @@ dependencies = [ { name = "click-option-group" }, { name = "distro" }, { name = "email-validator" }, - { name = "gunicorn" }, { name = "imas-python" }, { name = "netcdf4" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, @@ -1195,6 +1197,7 @@ all = [ { name = "flask-cors" }, { name = "flask-mail" }, { name = "flask-restx" }, + { name = "gunicorn" }, { name = "imas-validator" }, { name = "psycopg2-binary" }, { name = "python-magic" }, @@ -1238,6 +1241,7 @@ server = [ { name = "flask-cors" }, { name = "flask-mail" }, { name = "flask-restx" }, + { name = "gunicorn" }, { name = "python-magic" }, { name = "redis" }, { name = "simplejson" }, @@ -1269,9 +1273,9 @@ requires-dist = [ { name = "flask-caching", marker = "extra == 'server'", specifier = ">=1.10" }, { name = "flask-compress", marker = "extra == 'server'", specifier = ">=1.12" }, { name = "flask-cors", marker = "extra == 'server'", specifier = ">=3" }, - { name = "flask-mail", marker = "extra == 'server'", specifier = "~=0.9.1" }, + { name = "flask-mail", marker = "extra == 'server'", specifier = ">=0.10.0" }, { name = "flask-restx", marker = "extra == 'server'", specifier = ">=1.0.0" }, - { name = "gunicorn", specifier = ">=26.2.0" }, + { name = "gunicorn", marker = "extra == 'server'", specifier = ">=26.2.0" }, { name = "imas-python", specifier = ">=2.0.1" }, { name = "imas-simdb", extras = ["auth-ad", "auth-keycloak", "auth-ldap"], marker = "extra == 'auth'" }, { name = "imas-simdb", extras = ["imas-validator", "postgres", "server"], marker = "extra == 'all'" }, From 27e82ac93cad6d32af9663ab8f9e10cd44f0089f Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Fri, 18 Sep 2026 16:54:50 +0200 Subject: [PATCH 06/30] .gitignore: add common data folders --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 64eae04e..c487219e 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ src/simdb/_version.py *.egg-info *.egg *.whl +/sims +/upload_folder From f88888934e83406d9ac99461f12bb6ef4cfee544 Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Fri, 18 Sep 2026 16:56:49 +0200 Subject: [PATCH 07/30] scripts/simdb{,_server}: remove redundant scripts uv already installs the ones defined in pyproject.toml, under [project.scripts] --- .dockerignore | 2 ++ docs/how-to/operate-server/run-dev-server.md | 4 ++-- scripts/simdb | 7 ------- scripts/simdb_server | 5 ----- 4 files changed, 4 insertions(+), 14 deletions(-) delete mode 100755 scripts/simdb delete mode 100755 scripts/simdb_server diff --git a/.dockerignore b/.dockerignore index 0dbf8007..11605bfc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -17,3 +17,5 @@ ci/ Dockerfile Dockerfile.dev docker-compose*.yml +/sims +/upload_folder diff --git a/docs/how-to/operate-server/run-dev-server.md b/docs/how-to/operate-server/run-dev-server.md index 0fa5ab62..c5bfef5d 100644 --- a/docs/how-to/operate-server/run-dev-server.md +++ b/docs/how-to/operate-server/run-dev-server.md @@ -38,5 +38,5 @@ Each API version publishes Swagger UI documentation, for example ## Troubleshooting the port If the server cannot bind to port 5000, another service is using it. Stop that -service, or change the port in the `simdb_server` script (find it with -`which simdb_server`). See also [Troubleshooting](../../troubleshooting.md). +service, or change the `server.port` setting in your SimDB configuration. See +also [Troubleshooting](../../troubleshooting.md). diff --git a/scripts/simdb b/scripts/simdb deleted file mode 100755 index 806ac02b..00000000 --- a/scripts/simdb +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env python3 -# PYTHON_ARGCOMPLETE_OK - -from simdb.cli import simdb - - -simdb.main() diff --git a/scripts/simdb_server b/scripts/simdb_server deleted file mode 100755 index 84d5816e..00000000 --- a/scripts/simdb_server +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env python3 - -from simdb.remote.wsgi import run - -run() From f6134364310e656029a54ff8eb30c7c6759930a3 Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Fri, 18 Sep 2026 17:03:52 +0200 Subject: [PATCH 08/30] deps: drop unused flask-mail --- dev_requirements.txt | 1 - pyproject.toml | 1 - requirements.txt | 1 - uv.lock | 16 ---------------- 4 files changed, 19 deletions(-) diff --git a/dev_requirements.txt b/dev_requirements.txt index 031eeb99..17444928 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -15,7 +15,6 @@ distro==1.8.0 setuptools>=59 pytest-cov~=3.0 email-validator~=1.1 -flask-mail~=0.9.1 semantic-version>=2.8 click>=7.0,<=8.1.8 PyJWT>=2.3 diff --git a/pyproject.toml b/pyproject.toml index 656ad3c5..46460a95 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,6 @@ server = [ "flask-caching>=1.10", "flask-compress>=1.12", "flask-cors>=3", - "flask-mail>=0.10.0", "flask-restx>=1.0.0", "gunicorn>=26.2.0", "python-magic~=0.4", diff --git a/requirements.txt b/requirements.txt index 26bef65d..cd91fa13 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,6 @@ distro==1.8.0 setuptools>=59 pytest-cov~=3.0 email-validator~=1.1 -flask-mail~=0.9.1 semantic-version>=2.8 click>=7.0,<=8.1.8 PyJWT>=2.3 diff --git a/uv.lock b/uv.lock index d6713178..d3fa4577 100644 --- a/uv.lock +++ b/uv.lock @@ -892,19 +892,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/49/55/5bb1a2d918e9f02f131e47a59032bae70e48050e986e941511fd737a935c/flask_cors-6.0.5-py3-none-any.whl", hash = "sha256:68fcf75693e961f3af26683b23c4b9a8fb6b64de17d20d0c37b95e8de7ab2ed8", size = 16692, upload-time = "2026-06-08T20:20:16.247Z" }, ] -[[package]] -name = "flask-mail" -version = "0.10.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "blinker" }, - { name = "flask" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ba/29/e92dc84c675d1e8d260d5768eb3fb65c70cbd33addecf424187587bee862/flask_mail-0.10.0.tar.gz", hash = "sha256:44083e7b02bbcce792209c06252f8569dd5a325a7aaa76afe7330422bd97881d", size = 8152, upload-time = "2024-05-23T22:30:12.612Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/c0/a81083da779f482494d49195d8b6c9fde21072558253e4a9fb2ec969c3c1/flask_mail-0.10.0-py3-none-any.whl", hash = "sha256:a451e490931bb3441d9b11ebab6812a16bfa81855792ae1bf9c1e1e22c4e51e7", size = 8529, upload-time = "2024-05-23T22:30:10.962Z" }, -] - [[package]] name = "flask-restx" version = "1.3.2" @@ -1195,7 +1182,6 @@ all = [ { name = "flask-caching" }, { name = "flask-compress" }, { name = "flask-cors" }, - { name = "flask-mail" }, { name = "flask-restx" }, { name = "gunicorn" }, { name = "imas-validator" }, @@ -1239,7 +1225,6 @@ server = [ { name = "flask-caching" }, { name = "flask-compress" }, { name = "flask-cors" }, - { name = "flask-mail" }, { name = "flask-restx" }, { name = "gunicorn" }, { name = "python-magic" }, @@ -1273,7 +1258,6 @@ requires-dist = [ { name = "flask-caching", marker = "extra == 'server'", specifier = ">=1.10" }, { name = "flask-compress", marker = "extra == 'server'", specifier = ">=1.12" }, { name = "flask-cors", marker = "extra == 'server'", specifier = ">=3" }, - { name = "flask-mail", marker = "extra == 'server'", specifier = ">=0.10.0" }, { name = "flask-restx", marker = "extra == 'server'", specifier = ">=1.0.0" }, { name = "gunicorn", marker = "extra == 'server'", specifier = ">=26.2.0" }, { name = "imas-python", specifier = ">=2.0.1" }, From f3b46a55d7d1e49144de2058a8347893ba75a81a Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Fri, 18 Sep 2026 17:17:08 +0200 Subject: [PATCH 09/30] docker: remove docker-compose-pyver.yml --- docker-compose-pyver.yml | 54 ------------------- docs/how-to/operate-server/run-with-docker.md | 11 ++-- 2 files changed, 4 insertions(+), 61 deletions(-) delete mode 100644 docker-compose-pyver.yml diff --git a/docker-compose-pyver.yml b/docker-compose-pyver.yml deleted file mode 100644 index 63aa89ce..00000000 --- a/docker-compose-pyver.yml +++ /dev/null @@ -1,54 +0,0 @@ -# This docker-compose-pyver.yml extends the base docker-compose.yml -# to run three web services for different PYVER (3.11 and 3.13, 3.12 is the default) -# but they all use the same redis and postgres service. -# Run `docker compose -f docker-compose-workers.yml up` to start it up. -include: - - docker-compose.yml - -services: - migrations-311: - extends: - service: migrations - build: - args: - PYVER: "3.11" - - migrations-313: - extends: - service: migrations - build: - args: - PYVER: "3.13" - - web-311: - extends: - service: web - build: - args: - PYVER: "3.11" - ports: !override - - "5001:5000" - depends_on: !override - # !override is necessary to remove the original migrations from depends_on - redis: - condition: service_healthy - postgres: - condition: service_healthy - migrations-311: - condition: service_completed_successfully - - web-313: - extends: - service: web - build: - args: - PYVER: "3.13" - ports: !override - - "5003:5000" - depends_on: !override - redis: - condition: service_healthy - postgres: - condition: service_healthy - migrations-313: - condition: service_completed_successfully diff --git a/docs/how-to/operate-server/run-with-docker.md b/docs/how-to/operate-server/run-with-docker.md index 0ad372a5..d7861fb8 100644 --- a/docs/how-to/operate-server/run-with-docker.md +++ b/docs/how-to/operate-server/run-with-docker.md @@ -113,14 +113,11 @@ docker compose --profile with_workers up --build They use the `[celery]` broker and result backend from `config/simdb.cfg`. -## Testing against multiple Python versions +## Building against a different Python version -The image accepts a `PYVER` build argument (the default is 3.12). -`docker-compose-pyver.yml` extends the base setup to build the server against -several Python versions at once, sharing one PostgreSQL and Redis instance. It -publishes a `web-311` service (Python 3.11, on port 5001) and a `web-313` -service (Python 3.13, on port 5003) alongside the default 3.12 service: +The image accepts a `PYVER` build argument, which selects the Python version +used as the build base; the default is 3.12: ```bash -docker compose -f docker-compose-pyver.yml up --build +docker build --build-arg PYVER=3.11 . ``` From 4475eb61befef032f0bd4e142f4394c774a9d9c0 Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Fri, 18 Sep 2026 17:52:39 +0200 Subject: [PATCH 10/30] .gitignore, .dockerignore: cleanup --- .dockerignore | 3 +-- .gitignore | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.dockerignore b/.dockerignore index 11605bfc..50cc8107 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,9 +8,8 @@ .coverage **/__pycache__ **/*.pyc -*.egg-info +**/*.egg-info *.log -upload_folder/ docs/ tests/ ci/ diff --git a/.gitignore b/.gitignore index c487219e..ef3af8fb 100644 --- a/.gitignore +++ b/.gitignore @@ -11,8 +11,6 @@ environment.yml git_update runtests sdb -src/simdb/cli/runtests -src/simdb.egg-info test.yml test4_manifest.yml test7_manifest.yml From b687b99687fb82a4d185bb52fc555bd514660efe Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Mon, 21 Sep 2026 11:06:47 +0200 Subject: [PATCH 11/30] add gunicorn.conf.py --- Dockerfile | 4 ++-- docker-compose.yml | 6 ++++++ docker/gunicorn.conf.py | 11 +++++++++++ docs/how-to/operate-server/run-with-docker.md | 13 +++++++++++++ 4 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 docker/gunicorn.conf.py diff --git a/Dockerfile b/Dockerfile index 85a31b51..8bf09e92 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,6 +32,7 @@ LABEL org.opencontainers.image.title="SimDB" \ # Add the project source and finish the sync. ENV SETUPTOOLS_SCM_PRETEND_VERSION="${APP_VERSION}" COPY alembic.ini ./ +COPY docker/gunicorn.conf.py ./docker/gunicorn.conf.py COPY src/ ./src/ RUN uv sync --locked --extra all @@ -40,5 +41,4 @@ ENV SIMDB_SITE_CONFIG_PATH=/app/config/simdb.cfg EXPOSE 5000 # Run under Gunicorn rather than the Werkzeug dev server -CMD ["uv", "run", "gunicorn", "--bind=0.0.0.0:5000", "--workers=3", "--access-logfile=-", "--error-logfile=-", "simdb.remote.wsgi:app"] - +CMD ["uv", "run", "gunicorn", "--config=/app/docker/gunicorn.conf.py", "simdb.remote.wsgi:app"] diff --git a/docker-compose.yml b/docker-compose.yml index f488fead..c605772e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,9 +23,15 @@ services: container_name: ${SIMDB_CONTAINER_NAME:-simdb-web} environment: IMAS_LOCAL_HOSTS: localhost + GUNICORN_BIND: ${GUNICORN_BIND:-0.0.0.0:5000} + GUNICORN_WORKERS: ${GUNICORN_WORKERS:-3} + GUNICORN_WORKER_CLASS: ${GUNICORN_WORKER_CLASS:-gthread} + GUNICORN_THREADS: ${GUNICORN_THREADS:-4} + GUNICORN_TIMEOUT: ${GUNICORN_TIMEOUT:-120} ports: - "5000:5000" volumes: + - ./docker/gunicorn.conf.py:/app/docker/gunicorn.conf.py:ro - ./validation:/app/validation:ro - ./config:/app/config:ro - ./tmp/partition_data:/data/simdb/partition:ro diff --git a/docker/gunicorn.conf.py b/docker/gunicorn.conf.py new file mode 100644 index 00000000..fafe68ce --- /dev/null +++ b/docker/gunicorn.conf.py @@ -0,0 +1,11 @@ +import os + +# Binding: serve via TCP "0.0.0.0:5000" for Docker or network, +# or via unix socket "unix:/var/run/simdb.sock" for bare-metal and same host. +bind = os.environ.get("GUNICORN_BIND", "0.0.0.0:5000") +workers = int(os.environ.get("GUNICORN_WORKERS", "3")) +worker_class = os.environ.get("GUNICORN_WORKER_CLASS", "gthread") +threads = int(os.environ.get("GUNICORN_THREADS", "4")) +timeout = int(os.environ.get("GUNICORN_TIMEOUT", "120")) +accesslog = "-" +errorlog = "-" diff --git a/docs/how-to/operate-server/run-with-docker.md b/docs/how-to/operate-server/run-with-docker.md index d7861fb8..db3d138e 100644 --- a/docs/how-to/operate-server/run-with-docker.md +++ b/docs/how-to/operate-server/run-with-docker.md @@ -76,6 +76,19 @@ match the published port. See the [server configuration reference](../../reference/server-configuration.md) for all options. +### Gunicorn runtime + +The `web` service runs Gunicorn with three `gthread` workers, four threads per +worker, and a 120-second request timeout by default. Override these settings +through the environment when starting Compose: + +```bash +GUNICORN_WORKERS=4 GUNICORN_THREADS=8 GUNICORN_TIMEOUT=180 docker compose up --build +``` + +The available variables are `GUNICORN_BIND`, `GUNICORN_WORKERS`, +`GUNICORN_WORKER_CLASS`, `GUNICORN_THREADS`, and `GUNICORN_TIMEOUT`. + ## Start ```bash From 220bbb94241525da4e8aff445106848862e9e6d4 Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Mon, 21 Sep 2026 12:01:53 +0200 Subject: [PATCH 12/30] simdb instance: move nginx.dev.conf to docker/ --- docker-compose-dev.yml | 2 +- {deploy => docker}/nginx.dev.conf.template | 0 scripts/simdb-instance | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename {deploy => docker}/nginx.dev.conf.template (100%) diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 6624394a..009647f8 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -80,7 +80,7 @@ services: org.simdb.git.branch: ${SIMDB_GIT_BRANCH:-unknown} org.simdb.git.commit: ${SIMDB_GIT_COMMIT:-unknown} volumes: - - ./deploy/nginx.dev.conf.template:/etc/nginx/templates/default.conf.template:ro + - ./docker/nginx.dev.conf.template:/etc/nginx/templates/default.conf.template:ro depends_on: web: condition: service_healthy diff --git a/deploy/nginx.dev.conf.template b/docker/nginx.dev.conf.template similarity index 100% rename from deploy/nginx.dev.conf.template rename to docker/nginx.dev.conf.template diff --git a/scripts/simdb-instance b/scripts/simdb-instance index d9dd3c41..ffe4d0ee 100755 --- a/scripts/simdb-instance +++ b/scripts/simdb-instance @@ -124,7 +124,7 @@ prepare_worktree() { for required_file in \ Dockerfile.dev \ docker-compose-dev.yml \ - deploy/nginx.dev.conf.template; do + docker/nginx.dev.conf.template; do [[ -f "$worktree/$required_file" ]] || die "$required_file is missing from commit $commit" done From cc9b189dcc1cb25edac0e8cc440ee3ccc0b59db4 Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Mon, 21 Sep 2026 12:03:05 +0200 Subject: [PATCH 13/30] phase out legacy SysV init examples and docs Mention SimDB-Dashboard to provide nginx proxy service --- docker/proxy_params | 4 -- docker/simdb.nginx | 9 --- docs/how-to/operate-server/enable-ssl.md | 19 +++++- .../run-behind-nginx-gunicorn.md | 57 ++++++----------- src/simdb/remote/scripts/simdb.initd | 62 ------------------- src/simdb/remote/scripts/simdb.nginx | 9 --- 6 files changed, 35 insertions(+), 125 deletions(-) delete mode 100644 docker/proxy_params delete mode 100644 docker/simdb.nginx delete mode 100755 src/simdb/remote/scripts/simdb.initd delete mode 100644 src/simdb/remote/scripts/simdb.nginx diff --git a/docker/proxy_params b/docker/proxy_params deleted file mode 100644 index df75bc5d..00000000 --- a/docker/proxy_params +++ /dev/null @@ -1,4 +0,0 @@ -proxy_set_header Host $http_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; diff --git a/docker/simdb.nginx b/docker/simdb.nginx deleted file mode 100644 index a2de9f0d..00000000 --- a/docker/simdb.nginx +++ /dev/null @@ -1,9 +0,0 @@ -server { - listen 80; - server_name localhost; - - location / { - include proxy_params; - proxy_pass http://simdb:5000; - } -} diff --git a/docs/how-to/operate-server/enable-ssl.md b/docs/how-to/operate-server/enable-ssl.md index 7078c5be..aa114fdc 100644 --- a/docs/how-to/operate-server/enable-ssl.md +++ b/docs/how-to/operate-server/enable-ssl.md @@ -3,7 +3,12 @@ A production SimDB server must serve over HTTPS. There are two ways to enable SSL, depending on how you run the server. -## Option A: TLS at Nginx (recommended) +**For container deployments**, the sister project +[SimDB-Dashboard](https://github.com/iterorganization/SimDB-Dashboard) provides +a complete nginx configuration, including TLS, that proxies `/scenarios/api` to +the SimDB backend. This is the recommended approach for modern deployments. + +## Option A: TLS at Nginx When [running behind Nginx and Gunicorn](run-behind-nginx-gunicorn.md), let Nginx terminate TLS. Change `/etc/nginx/conf.d/simdb.conf` to listen on 443 and @@ -21,8 +26,16 @@ server { ssl_certificate /etc/pki/nginx/server.crt; ssl_certificate_key /etc/pki/nginx/private/server.key; - location / { - include proxy_params; + location /scenarios/api { + # Typical proxy params + proxy_set_header Host $http_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; + + # Inform simdb backend to add this prefix in their responses + proxy_set_header X-Forwarded-Prefix /scenarios/api; + proxy_pass http://unix:/var/run/simdb.sock; } } diff --git a/docs/how-to/operate-server/run-behind-nginx-gunicorn.md b/docs/how-to/operate-server/run-behind-nginx-gunicorn.md index 8de94e2d..3755e60b 100644 --- a/docs/how-to/operate-server/run-behind-nginx-gunicorn.md +++ b/docs/how-to/operate-server/run-behind-nginx-gunicorn.md @@ -4,58 +4,39 @@ In production, run the SimDB server as a WSGI service behind a dedicated web server. This guide uses Gunicorn as the WSGI server and Nginx as the proxy/load-balancer. It assumes Nginx and Gunicorn are already installed. -## Set up the Gunicorn service +**For container deployments**, the sister project +[SimDB-Dashboard](https://github.com/iterorganization/SimDB-Dashboard) provides +a complete nginx configuration that proxies `/scenarios/api` to the SimDB +backend. This is the recommended approach for modern deployments. -Copy the init script from `src/simdb/remote/scripts/simdb.initd` in the SimDB -install directory to `/etc/init.d/simdb`. +## Set up Nginx (bare-metal) -Edit two lines in it: - -- `USER=simdb` to the user the workers should run as. -- `DAEMON=/home/simdb/venv/bin/gunicorn` to the `gunicorn` in your virtual - environment (find it with `which gunicorn` while the venv is active). - -Start and check the service: - -```bash -service simdb start -service simdb status -``` - -## Set up Nginx - -Create `/etc/nginx/conf.d/simdb.conf`: +Create `/etc/nginx/conf.d/simdb.conf`, for example: ```nginx server { listen 80; server_name localhost; # or the server's address - location / { - include proxy_params; + location /scenarios/api { + # Typical proxy params + proxy_set_header Host $http_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; + + # Inform simdb backend to add this prefix in their responses + proxy_set_header X-Forwarded-Prefix /scenarios/api; + proxy_pass http://unix:/var/run/simdb.sock; + # For TCP proxy instead (e.g., if gunicorn binds to 0.0.0.0:5000): + # proxy_pass http://localhost:5000; } } ``` -The packaged `src/simdb/remote/scripts/simdb.nginx` can be copied instead. The -`proxy_pass` target must match the `BIND` value in the init script. - -If `/etc/nginx/proxy_params` does not exist, create it: - -```nginx -proxy_set_header Host $http_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; -``` - Make sure `/etc/nginx/nginx.conf` includes `/etc/nginx/conf.d/*.conf` inside its -`http {}` block, then reload: - -```bash -service nginx restart -``` +`http {}` block, then reload your nginx service. ## Allow large uploads diff --git a/src/simdb/remote/scripts/simdb.initd b/src/simdb/remote/scripts/simdb.initd deleted file mode 100755 index a5fa574e..00000000 --- a/src/simdb/remote/scripts/simdb.initd +++ /dev/null @@ -1,62 +0,0 @@ -#! /bin/bash -### BEGIN INIT INFO -# Provides: simdb -# Required-Start: nginx -# Required-Stop: -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: SimDB Flask App -# Description: The gunicorn process that receives HTTP requests -# from nginx -# -### END INIT INFO -# -# Author: Jonathan Hollocombe -# -APPNAME=simdb -USER=simdb -PATH=/bin:/usr/bin:/sbin:/usr/sbin -APPMODULE=simdb.remote.wsgi:app -DAEMON=/home/simdb/venv/bin/gunicorn -BIND=unix:/var/run/simdb.sock -PIDFILE=/var/run/simdb.pid -LOGFILE=/var/log/simdb.log -WORKERS=3 - - -. /etc/rc.d/init.d/functions - - -if [ -e "/etc/default/$APPNAME" ] -then - . /etc/default/$APPNAME -fi - - -case "$1" in - start) - echo -n "Starting deferred execution scheduler" "$APPNAME" - $DAEMON --daemon --bind=$BIND --pid=$PIDFILE --workers=$WORKERS --user=$USER --log-file=$LOGFILE $APPMODULE - echo - exit $? - ;; - stop) - echo -n "Stopping deferred execution scheduler" "APPNAME" - killproc -p $PIDFILE gunicorn - echo - exit $? - ;; - force-reload|restart) - $0 stop - $0 start - ;; - status) - status -p $PIDFILE gunicorn && exit 0 || exit $? - ;; - *) - echo "Usage: /etc/init.d/$APPNAME {start|stop|restart|force-reload|status}" - exit 1 - ;; -esac - -exit 0 diff --git a/src/simdb/remote/scripts/simdb.nginx b/src/simdb/remote/scripts/simdb.nginx deleted file mode 100644 index 459b5a6d..00000000 --- a/src/simdb/remote/scripts/simdb.nginx +++ /dev/null @@ -1,9 +0,0 @@ -server { - listen 80; - server_name localhost; - - location / { - include proxy_params; - proxy_pass http://unix:/var/run/simdb.sock; - } -} From 8c4adb922850c8b3b4602fb4cd81a9d76e0db400 Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Tue, 22 Sep 2026 13:37:41 +0200 Subject: [PATCH 14/30] docker: pin base image tags --- Dockerfile | 3 +-- docker-compose-dev.yml | 1 + docker-compose.yml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8bf09e92..212fc25e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,4 @@ -ARG PYVER=3.12 -FROM ghcr.io/astral-sh/uv:python${PYVER}-trixie-slim +FROM ghcr.io/astral-sh/uv:0.12.17-python3.12-trixie-slim@sha256:9a59bb7206905ccaae4f7dab222fbac47c125a21e5fc16f43f427cd6c940ade3 ENV UV_NO_DEV=1 \ UV_LINK_MODE=copy \ diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 009647f8..6849e27d 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -172,6 +172,7 @@ services: - POSTGRES_USER=simdb - POSTGRES_PASSWORD=simdb - POSTGRES_DB=simdb + - POSTGRES_INITDB_ARGS=--locale-provider=icu --icu-locale=en_US.utf8 # workaround for alpine not having locale volumes: - postgres_data:/var/lib/postgresql/data healthcheck: diff --git a/docker-compose.yml b/docker-compose.yml index c605772e..30f06049 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -88,7 +88,7 @@ services: restart: unless-stopped redis: - image: redis:8-alpine + image: redis:8.6.6-alpine3.23@sha256:23a604374ece8069b34131faac7a1f05ae73c64280e46af1de844f29af2846d4 volumes: - redis_data:/data healthcheck: @@ -99,7 +99,7 @@ services: restart: unless-stopped postgres: - image: postgres:16-alpine + image: postgres:16.15-alpine3.23@sha256:621a761097839bdb50207afd6b87a72f38e2d718dd46c3d744828d8917c4f1e0 environment: - POSTGRES_USER=simdb - POSTGRES_PASSWORD=simdb From 39e1c369dcdff7f2a0f6b77c994a7436c3b5ab92 Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Tue, 22 Sep 2026 13:39:44 +0200 Subject: [PATCH 15/30] docker-compose.yaml: bugfix postgres initdb issue migrations service should wait for a healthy postgres. postgres should not need `locale` command, it doesn't exist on alpine. --- docker-compose.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 30f06049..8665b7e2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,7 +10,8 @@ services: environment: DATABASE_URL: postgresql+psycopg2://simdb:simdb@postgres:5432/simdb depends_on: - - postgres + postgres: + condition: service_healthy command: uv run alembic upgrade head restart: "no" @@ -104,13 +105,15 @@ services: - POSTGRES_USER=simdb - POSTGRES_PASSWORD=simdb - POSTGRES_DB=simdb + - POSTGRES_INITDB_ARGS=--locale-provider=icu --icu-locale=en_US.utf8 # workaround for alpine not having locale volumes: - postgres_data:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U simdb"] - interval: 1s + test: ["CMD-SHELL", "pg_isready -U simdb -d simdb"] + interval: 2s timeout: 5s - retries: 5 + retries: 15 + start_period: 30s restart: unless-stopped volumes: From 9b8d21e8d0acf37ed260e46146f7960997a3c724 Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Tue, 22 Sep 2026 14:04:28 +0200 Subject: [PATCH 16/30] Dockerfile: setuptools tweak SETUPTOOLS_SCM_PRETEND_VERSION_FOR_IMAS_SIMDB --- Dockerfile | 2 +- Dockerfile.dev | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 212fc25e..2e17fba1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ LABEL org.opencontainers.image.title="SimDB" \ io.simdb.component="server" # Add the project source and finish the sync. -ENV SETUPTOOLS_SCM_PRETEND_VERSION="${APP_VERSION}" +ENV SETUPTOOLS_SCM_PRETEND_VERSION_FOR_IMAS_SIMDB="${APP_VERSION}" COPY alembic.ini ./ COPY docker/gunicorn.conf.py ./docker/gunicorn.conf.py COPY src/ ./src/ diff --git a/Dockerfile.dev b/Dockerfile.dev index 0cbdb8ae..7c33fe4c 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -3,8 +3,7 @@ FROM ghcr.io/astral-sh/uv:python${PYVER}-trixie-slim ENV UV_NO_DEV=1 \ UV_LINK_MODE=copy \ - PYTHONUNBUFFERED=1 \ - SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0 + PYTHONUNBUFFERED=1 WORKDIR /app @@ -21,6 +20,9 @@ COPY uv.lock pyproject.toml ./ RUN uv sync --locked --no-install-project --no-build --extra all # Add the project source and finish the sync. +ARG APP_VERSION=0.0.0 +ENV SETUPTOOLS_SCM_PRETEND_VERSION_FOR_IMAS_SIMDB="${APP_VERSION}" + ARG SIMDB_GIT_BRANCH=unknown ARG SIMDB_GIT_COMMIT=unknown ARG SIMDB_DEPLOYMENT=local From dff17974a4e4c9083052490d909fb75d95b11dc2 Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Tue, 22 Sep 2026 14:00:26 +0200 Subject: [PATCH 17/30] Dockerfile: multi-stage build + server build: with all compile dependencies. server: with only execute capabilities and OCI labels. --- Dockerfile | 48 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2e17fba1..4922723c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,6 @@ -FROM ghcr.io/astral-sh/uv:0.12.17-python3.12-trixie-slim@sha256:9a59bb7206905ccaae4f7dab222fbac47c125a21e5fc16f43f427cd6c940ade3 +# Build stage: Install dependencies and prepare the application +FROM ghcr.io/astral-sh/uv:0.12.17-python3.12-trixie-slim@sha256:9a59bb7206905ccaae4f7dab222fbac47c125a21e5fc16f43f427cd6c940ade3 \ + AS build ENV UV_NO_DEV=1 \ UV_LINK_MODE=copy \ @@ -21,13 +23,6 @@ RUN uv sync --locked --no-install-project --no-build --extra all ARG APP_VERSION=0.0.0 -LABEL org.opencontainers.image.title="SimDB" \ - org.opencontainers.image.description="ITER Simulation Management Tool" \ - org.opencontainers.image.source="https://github.com/iterorganization/SimDB" \ - org.opencontainers.image.licenses="LGPL-3.0-only" \ - org.opencontainers.image.version="${APP_VERSION}" \ - io.simdb.component="server" - # Add the project source and finish the sync. ENV SETUPTOOLS_SCM_PRETEND_VERSION_FOR_IMAS_SIMDB="${APP_VERSION}" COPY alembic.ini ./ @@ -37,6 +32,43 @@ RUN uv sync --locked --extra all ENV SIMDB_SITE_CONFIG_PATH=/app/config/simdb.cfg +# Runtime stage: Minimal image with only runtime dependencies +FROM ghcr.io/astral-sh/uv:0.12.17-python3.12-trixie-slim@sha256:9a59bb7206905ccaae4f7dab222fbac47c125a21e5fc16f43f427cd6c940ade3 \ + AS service + +ENV UV_NO_DEV=1 \ + UV_LINK_MODE=copy \ + UV_COMPILE_BYTECODE=1 \ + PYTHONUNBUFFERED=1 + +WORKDIR /app + +# Install only runtime dependencies (no build-essential, no *-dev variants, etc.) +RUN apt-get update && apt-get install -y --no-install-recommends \ + libpq5 \ + libldap2 \ + libsasl2-2 \ + libmagic1 \ + && rm -rf /var/lib/apt/lists/* + +# Copy the prepared application and dependencies from build stage +COPY --from=build /app/.venv /app/.venv +COPY --from=build /app/alembic.ini ./ +COPY --from=build /app/docker/gunicorn.conf.py ./docker/gunicorn.conf.py +COPY --from=build /app/src/ ./src/ +COPY --from=build /app/alembic/ ./alembic/ + +ARG APP_VERSION=0.0.0 + +LABEL org.opencontainers.image.title="SimDB" \ + org.opencontainers.image.description="SimDB Server — ITER simulation management tool" \ + org.opencontainers.image.source="https://github.com/iterorganization/SimDB" \ + org.opencontainers.image.licenses="LGPL-3.0-only" \ + org.opencontainers.image.version="${APP_VERSION}" \ + io.simdb.component="server" + +ENV SIMDB_SITE_CONFIG_PATH=/app/config/simdb.cfg + EXPOSE 5000 # Run under Gunicorn rather than the Werkzeug dev server From 54cfabe350eb1bc7449ad5d913875578cf365d16 Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Tue, 22 Sep 2026 16:23:03 +0200 Subject: [PATCH 18/30] Dockerfile: add validation stage --- Dockerfile | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 4922723c..51438670 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,6 +32,38 @@ RUN uv sync --locked --extra all ENV SIMDB_SITE_CONFIG_PATH=/app/config/simdb.cfg +# Validation stage: run linting and tests using the project dev environment. +FROM ghcr.io/astral-sh/uv:0.12.17-python3.12-trixie-slim@sha256:9a59bb7206905ccaae4f7dab222fbac47c125a21e5fc16f43f427cd6c940ade3 \ + AS test + +ENV UV_NO_DEV=1 \ + UV_LINK_MODE=copy \ + UV_COMPILE_BYTECODE=1 \ + PYTHONUNBUFFERED=1 + +WORKDIR /app + +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + libpq-dev \ + libldap2-dev \ + libsasl2-dev \ + libmagic1 \ + && rm -rf /var/lib/apt/lists/* + +COPY uv.lock pyproject.toml ./ +COPY alembic.ini ./ +COPY docker/gunicorn.conf.py ./docker/gunicorn.conf.py +COPY src/ ./src/ +COPY tests/ ./tests/ + +RUN uv sync --locked --group dev --extra all + +RUN uv run python -m ruff format --check +RUN uv run python -m ruff check +RUN uv run python -m ty check src +RUN uv run python -m pytest --cov=simdb --cov-report=term-missing --cov-report=xml:coverage.xml --cov-report=html:htmlcov -v --tb=short + # Runtime stage: Minimal image with only runtime dependencies FROM ghcr.io/astral-sh/uv:0.12.17-python3.12-trixie-slim@sha256:9a59bb7206905ccaae4f7dab222fbac47c125a21e5fc16f43f427cd6c940ade3 \ AS service @@ -56,7 +88,6 @@ COPY --from=build /app/.venv /app/.venv COPY --from=build /app/alembic.ini ./ COPY --from=build /app/docker/gunicorn.conf.py ./docker/gunicorn.conf.py COPY --from=build /app/src/ ./src/ -COPY --from=build /app/alembic/ ./alembic/ ARG APP_VERSION=0.0.0 From 2076fbb0522541dfa327ac194143243e343f7bf2 Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Tue, 22 Sep 2026 16:45:50 +0200 Subject: [PATCH 19/30] Dockerfile: uv sync --no-dev only, UV_NO_DEV environment makes uv run throw warning "has no effect" --- Dockerfile | 14 ++++++-------- Dockerfile.dev | 8 ++++---- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/Dockerfile b/Dockerfile index 51438670..8a9b255f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,8 +2,7 @@ FROM ghcr.io/astral-sh/uv:0.12.17-python3.12-trixie-slim@sha256:9a59bb7206905ccaae4f7dab222fbac47c125a21e5fc16f43f427cd6c940ade3 \ AS build -ENV UV_NO_DEV=1 \ - UV_LINK_MODE=copy \ +ENV UV_LINK_MODE=copy \ UV_COMPILE_BYTECODE=1 \ PYTHONUNBUFFERED=1 @@ -19,7 +18,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # Install dependencies in their own layer, cached independently. COPY uv.lock pyproject.toml ./ -RUN uv sync --locked --no-install-project --no-build --extra all +RUN uv sync --locked --no-dev --no-install-project --no-build --extra all ARG APP_VERSION=0.0.0 @@ -28,7 +27,8 @@ ENV SETUPTOOLS_SCM_PRETEND_VERSION_FOR_IMAS_SIMDB="${APP_VERSION}" COPY alembic.ini ./ COPY docker/gunicorn.conf.py ./docker/gunicorn.conf.py COPY src/ ./src/ -RUN uv sync --locked --extra all +COPY alembic/ ./alembic/ +RUN uv sync --locked --no-dev --extra all ENV SIMDB_SITE_CONFIG_PATH=/app/config/simdb.cfg @@ -36,8 +36,7 @@ ENV SIMDB_SITE_CONFIG_PATH=/app/config/simdb.cfg FROM ghcr.io/astral-sh/uv:0.12.17-python3.12-trixie-slim@sha256:9a59bb7206905ccaae4f7dab222fbac47c125a21e5fc16f43f427cd6c940ade3 \ AS test -ENV UV_NO_DEV=1 \ - UV_LINK_MODE=copy \ +ENV UV_LINK_MODE=copy \ UV_COMPILE_BYTECODE=1 \ PYTHONUNBUFFERED=1 @@ -68,8 +67,7 @@ RUN uv run python -m pytest --cov=simdb --cov-report=term-missing --cov-report=x FROM ghcr.io/astral-sh/uv:0.12.17-python3.12-trixie-slim@sha256:9a59bb7206905ccaae4f7dab222fbac47c125a21e5fc16f43f427cd6c940ade3 \ AS service -ENV UV_NO_DEV=1 \ - UV_LINK_MODE=copy \ +ENV UV_LINK_MODE=copy \ UV_COMPILE_BYTECODE=1 \ PYTHONUNBUFFERED=1 diff --git a/Dockerfile.dev b/Dockerfile.dev index 7c33fe4c..fc8d73ee 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,8 +1,7 @@ ARG PYVER=3.12 FROM ghcr.io/astral-sh/uv:python${PYVER}-trixie-slim -ENV UV_NO_DEV=1 \ - UV_LINK_MODE=copy \ +ENV UV_LINK_MODE=copy \ PYTHONUNBUFFERED=1 WORKDIR /app @@ -17,7 +16,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # Install dependencies in their own layer, cached independently. COPY uv.lock pyproject.toml ./ -RUN uv sync --locked --no-install-project --no-build --extra all +RUN uv sync --locked --no-dev --no-install-project --no-build --extra all # Add the project source and finish the sync. ARG APP_VERSION=0.0.0 @@ -38,7 +37,8 @@ ENV SIMDB_GIT_COMMIT="${SIMDB_GIT_COMMIT}" COPY alembic.ini ./ COPY src/ ./src/ -RUN uv sync --locked --extra all +COPY alembic/ ./alembic/ +RUN uv sync --locked --no-dev --extra all ENV SIMDB_SITE_CONFIG_PATH=/app/config/simdb.cfg From f8f05d916a18a16861bc76227a2e429a1ede4771 Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Tue, 22 Sep 2026 18:09:02 +0200 Subject: [PATCH 20/30] Dockerfile: non-root user Celery was complaining loudly. --- Dockerfile | 19 ++++++++++++++----- Dockerfile.dev | 1 - 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8a9b255f..9c11ed25 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,6 @@ ENV SETUPTOOLS_SCM_PRETEND_VERSION_FOR_IMAS_SIMDB="${APP_VERSION}" COPY alembic.ini ./ COPY docker/gunicorn.conf.py ./docker/gunicorn.conf.py COPY src/ ./src/ -COPY alembic/ ./alembic/ RUN uv sync --locked --no-dev --extra all ENV SIMDB_SITE_CONFIG_PATH=/app/config/simdb.cfg @@ -67,6 +66,9 @@ RUN uv run python -m pytest --cov=simdb --cov-report=term-missing --cov-report=x FROM ghcr.io/astral-sh/uv:0.12.17-python3.12-trixie-slim@sha256:9a59bb7206905ccaae4f7dab222fbac47c125a21e5fc16f43f427cd6c940ade3 \ AS service +ARG APP_UID=1000 +ARG APP_GID=1000 + ENV UV_LINK_MODE=copy \ UV_COMPILE_BYTECODE=1 \ PYTHONUNBUFFERED=1 @@ -81,11 +83,16 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libmagic1 \ && rm -rf /var/lib/apt/lists/* +RUN groupadd --gid ${APP_GID} simdb \ + && useradd --uid ${APP_UID} --gid ${APP_GID} --create-home --shell /usr/sbin/nologin simdb \ + && mkdir -p /data/simdb/simulations /home/simdb/.gunicorn \ + && chown -R simdb:simdb /data/simdb /home/simdb /app + # Copy the prepared application and dependencies from build stage -COPY --from=build /app/.venv /app/.venv -COPY --from=build /app/alembic.ini ./ -COPY --from=build /app/docker/gunicorn.conf.py ./docker/gunicorn.conf.py -COPY --from=build /app/src/ ./src/ +COPY --from=build --chown=simdb:simdb /app/.venv /app/.venv +COPY --from=build --chown=simdb:simdb /app/alembic.ini ./ +COPY --from=build --chown=simdb:simdb /app/docker/gunicorn.conf.py ./docker/gunicorn.conf.py +COPY --from=build --chown=simdb:simdb /app/src/ ./src/ ARG APP_VERSION=0.0.0 @@ -98,6 +105,8 @@ LABEL org.opencontainers.image.title="SimDB" \ ENV SIMDB_SITE_CONFIG_PATH=/app/config/simdb.cfg +USER simdb + EXPOSE 5000 # Run under Gunicorn rather than the Werkzeug dev server diff --git a/Dockerfile.dev b/Dockerfile.dev index fc8d73ee..f381390a 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -37,7 +37,6 @@ ENV SIMDB_GIT_COMMIT="${SIMDB_GIT_COMMIT}" COPY alembic.ini ./ COPY src/ ./src/ -COPY alembic/ ./alembic/ RUN uv sync --locked --no-dev --extra all ENV SIMDB_SITE_CONFIG_PATH=/app/config/simdb.cfg From f0145b52c6a2beac6014da4642bae05384c8b2c6 Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Tue, 22 Sep 2026 18:07:19 +0200 Subject: [PATCH 21/30] cache.py: add TODO remove hard-coded config_file path --- src/simdb/remote/core/cache.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/simdb/remote/core/cache.py b/src/simdb/remote/core/cache.py index 64550951..ea3e15a5 100644 --- a/src/simdb/remote/core/cache.py +++ b/src/simdb/remote/core/cache.py @@ -5,7 +5,8 @@ from simdb.config import Config -config = Config("app.cfg") +# TODO remove hard-coded config_file path to "app.cfg" +config = Config(file_name="app.cfg") config.load() cache_options = { "CACHE_" + k.upper(): v for (k, v) in config.get_section("cache", {}).items() From ae5523c647b0bcf64ec2a15ace26cb13fda03f31 Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Tue, 22 Sep 2026 18:08:16 +0200 Subject: [PATCH 22/30] simdb.cfg: add cache.type=SimpleCache --- config/simdb.cfg | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/config/simdb.cfg b/config/simdb.cfg index 4a6816f5..4d77b30b 100644 --- a/config/simdb.cfg +++ b/config/simdb.cfg @@ -2,7 +2,11 @@ flask_env = development debug = True testing = True -secret_key = CHANGE ME +secret_key = CHANGE_ME + +[cache] +type = SimpleCache +default_timeout = 300 [authentication] type=none @@ -12,7 +16,7 @@ upload_folder = /data/simdb/simulations port = 5000 ssl_enabled = False authentication_type = None -admin_password=CHANGE_ME +admin_password = CHANGE_ME imas_remote_host = localhost [database] From d01caa825203b8e0fcc6bb0fccc4f889d8e85083 Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Wed, 23 Sep 2026 17:33:22 +0200 Subject: [PATCH 23/30] ci: add docker_image.yml --- .github/workflows/docker_image.yml | 148 +++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 .github/workflows/docker_image.yml diff --git a/.github/workflows/docker_image.yml b/.github/workflows/docker_image.yml new file mode 100644 index 00000000..ae7b8cae --- /dev/null +++ b/.github/workflows/docker_image.yml @@ -0,0 +1,148 @@ +name: Docker Image build and publish + +env: + CACHE_FROM: type=gha,scope=simdb-server + CACHE_TO: type=gha,mode=max,scope=simdb-server + SETUPTOOLS_SCM_OVERRIDES_FOR_IMAS_SIMDB: '{local_scheme = "no-local-version-strict"}' + +on: + # Triggers on pushes to develop, on pushed tags, and on pull requests targeting develop. + push: + branches: [ "develop" ] + tags: [ "*" ] + pull_request: + branches: [ "develop" ] + +jobs: + build: + name: Build, verify, and package + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.value }} + permissions: + contents: read + steps: + - name: Checkout code + uses: actions/checkout@v7 + with: + fetch-tags: true + fetch-depth: 0 + + - name: Get package version + id: version + run: echo "value=$(git describe --tags --long --always | sed -r 's/-([0-9]+)/.dev\1/;s/-/+/')" >> "$GITHUB_OUTPUT" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Build build image + uses: docker/build-push-action@v7 + with: + target: build + load: true + tags: simdb-server:build + build-args: APP_VERSION=${{ steps.version.outputs.value }} + cache-from: ${{ env.CACHE_FROM }} + cache-to: ${{ env.CACHE_TO }} + + - name: Build validation image + uses: docker/build-push-action@v7 + with: + target: test + load: true + tags: simdb-server:test + build-args: APP_VERSION=${{ steps.version.outputs.value }} + cache-from: ${{ env.CACHE_FROM }} + cache-to: ${{ env.CACHE_TO }} + + - name: Build service image + uses: docker/build-push-action@v7 + with: + target: service + load: true + tags: simdb-server:service + build-args: APP_VERSION=${{ steps.version.outputs.value }} + cache-from: ${{ env.CACHE_FROM }} + cache-to: ${{ env.CACHE_TO }} + + - name: Export service image tar + run: docker save -o "${{ runner.temp }}/simdb-server-service.tar" simdb-server:service + + - name: Upload service image artifact + uses: actions/upload-artifact@v7 + with: + name: simdb-server-service + path: ${{ runner.temp }}/simdb-server-service.tar + retention-days: 7 + + verify: + name: Verify Docker validation stage + runs-on: ubuntu-latest + needs: build + permissions: + contents: read + steps: + - name: Checkout code + uses: actions/checkout@v7 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Build validation stage only + uses: docker/build-push-action@v7 + with: + target: test + load: true + tags: simdb-server:test + build-args: APP_VERSION=0.0.0 + cache-from: ${{ env.CACHE_FROM }} + cache-to: ${{ env.CACHE_TO }} + + publish: + name: Publish service image + # Runs only for push events (branch/tag pushes), and is skipped for pull_request events. + if: ${{ github.event_name == 'push' }} + runs-on: ubuntu-latest + needs: build + permissions: + contents: write + packages: write + steps: + - name: Lowercase repo-owner + id: repo_owner + run: echo "value=${GITHUB_REPOSITORY_OWNER,,}" >> "$GITHUB_OUTPUT" + + - name: Download service image artifact + uses: actions/download-artifact@v8 + with: + name: simdb-server-service + path: ${{ runner.temp }} + + - name: Load service image + run: docker load -i "${{ runner.temp }}/simdb-server.tar" + + - name: Login to GitHub Container Registry + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ steps.repo_owner.outputs.value }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Tag and push service image with versioned tag and "nightly" + if: ${{ github.ref_type == 'branch' }} + run: | + docker tag simdb-server:service ghcr.io/${{ steps.repo_owner.outputs.value }}/simdb-server:${{ needs.build.outputs.version }} + docker tag simdb-server:service ghcr.io/${{ steps.repo_owner.outputs.value }}/simdb-server:nightly + docker push ghcr.io/${{ steps.repo_owner.outputs.value }}/simdb-server:${{ needs.build.outputs.version }} + docker push ghcr.io/${{ steps.repo_owner.outputs.value }}/simdb-server:nightly + + + # These steps run on tagged commits only + - name: Tag and push latest + if: ${{ github.ref_type == 'tag' }} + run: | + docker tag \ + simdb-server:service \ + ghcr.io/${{ steps.repo_owner.outputs.value }}/simdb-server:latest + docker push ghcr.io/${{ steps.repo_owner.outputs.value }}/simdb-server:latest + From 88b48f25c5d32b379be1c7d525b71b09b0406d81 Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Thu, 24 Sep 2026 11:06:16 +0200 Subject: [PATCH 24/30] stub-add --- docker/scripts/postgres-backup | 27 +++++++++++++++++++++++++++ docker/scripts/postgres-restore | 22 ++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100755 docker/scripts/postgres-backup create mode 100755 docker/scripts/postgres-restore diff --git a/docker/scripts/postgres-backup b/docker/scripts/postgres-backup new file mode 100755 index 00000000..fb97bf00 --- /dev/null +++ b/docker/scripts/postgres-backup @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -ne 1 ]]; then + echo "usage: $0 BACKUP_FILE" >&2 + exit 2 +fi + +backup_file="$1" +backup_dir="$(dirname "$backup_file")" +[[ -d "$backup_dir" ]] || { + echo "backup directory does not exist: $backup_dir" >&2 + exit 2 +} + +temporary_file="$(mktemp "${backup_file}.tmp.XXXXXX")" +trap 'rm -f "$temporary_file"' EXIT + +docker compose exec -T postgres \ + pg_dump \ + --username="${POSTGRES_USER:-simdb}" \ + --dbname="${POSTGRES_DB:-simdb}" \ + --format=custom \ + >"$temporary_file" + +mv "$temporary_file" "$backup_file" +trap - EXIT diff --git a/docker/scripts/postgres-restore b/docker/scripts/postgres-restore new file mode 100755 index 00000000..4bf3729b --- /dev/null +++ b/docker/scripts/postgres-restore @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -ne 1 ]]; then + echo "usage: $0 BACKUP_FILE" >&2 + exit 2 +fi + +backup_file="$1" +[[ -f "$backup_file" ]] || { + echo "backup file does not exist: $backup_file" >&2 + exit 2 +} + +docker compose exec -T postgres \ + pg_restore \ + --username="${POSTGRES_USER:-simdb}" \ + --dbname="${POSTGRES_DB:-simdb}" \ + --clean \ + --if-exists \ + --no-owner \ + <"$backup_file" From f71c3799d2c10bc3afd67a9470188180dd0bcc6e Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Thu, 24 Sep 2026 11:07:33 +0200 Subject: [PATCH 25/30] systemd: add compose layer and service files --- docker-compose.systemd.yml | 23 +++++++++++ docker/scripts/simdb-server.env.example | 24 +++++++++++ docker/scripts/simdb-server.service | 54 +++++++++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 docker-compose.systemd.yml create mode 100644 docker/scripts/simdb-server.env.example create mode 100644 docker/scripts/simdb-server.service diff --git a/docker-compose.systemd.yml b/docker-compose.systemd.yml new file mode 100644 index 00000000..ddf270ed --- /dev/null +++ b/docker-compose.systemd.yml @@ -0,0 +1,23 @@ +# Systemd override for docker-compose.yml. +# +# Uses the GHCR-published image instead of a local build. +# +# Set SIMDB_DASHBOARD_TAG in the environment file to pin a specific +# version. The default is the latest tag at the time of writing. +# +# Enable this override by including it in COMPOSE_FILE, +# after the original, e.g. +# COMPOSE_FILE="docker-compose.yml:docker-compose.systemd.yml" +# +services: + web: + # Systemd deployment should only manage a single container instance, docker requires + # container_name to be unique, so use it to ensure a fixed name. + container_name: ${SYSTEMD_CONTAINER_SIMDB:-simdb-server-systemd} + image: ${SIMDB_SERVER_IMAGE:-ghcr.io/iterorganization/simdb}:${SIMDB_SERVER_TAG:-latest} + logging: + driver: journald + options: + tag: simdb-server-systemd + postgres: + container_name: ${SYSTEMD_CONTAINER_POSTGRES:-simdb-postgres-systemd} diff --git a/docker/scripts/simdb-server.env.example b/docker/scripts/simdb-server.env.example new file mode 100644 index 00000000..c15b8b2a --- /dev/null +++ b/docker/scripts/simdb-server.env.example @@ -0,0 +1,24 @@ +## Environment file used by scripts/simdb-server.service. +## Everything here is commented out to show defaults. + +## Select compose file overrides. `docker compose` will use docker-compose.yml by default, +## unless COMPOSE_FILE or --file is set. +## +## The systemd variant (prefers online published image), default: +#COMPOSE_FILE=docker-compose.yml:docker-compose.systemd.yml + +## Optional: use a custom server container name (shows up in docker ps etc) +#SYSTEMD_CONTAINER_SIMDB=simdb-server-systemd + +## Choose image to use for the simdb-server container +#SIMDB_SERVER_TAG=latest +#SIMDB_SERVER_IMAGE=ghcr.io/iterorganization/simdb + +# Binding: serve via TCP "0.0.0.0:5000" for Docker or network, +#GUNICORN_BIND=0.0.0.0:5000 + +# Some other GUNICORN flags +#GUNICORN_WORKERS=3 +#GUNICORN_WORKER_CLASS=gthread +#GUNICORN_THREADS=4 +#GUNICORN_TIMEOUT=120 diff --git a/docker/scripts/simdb-server.service b/docker/scripts/simdb-server.service new file mode 100644 index 00000000..b924cf73 --- /dev/null +++ b/docker/scripts/simdb-server.service @@ -0,0 +1,54 @@ +# SimDB Server systemd service unit. + +[Unit] +Description=SimDB Server — ITER simulation management tool +Documentation=https://github.com/iterorganization/SimDB +Requires=docker.service +After=docker.service network-online.target +StartLimitIntervalSec=60 +StartLimitBurst=3 + +[Service] +Type=oneshot +RemainAfterExit=yes + +# --- paths ------------------------------------------------------------ +EnvironmentFile=/etc/simdb-server/simdb-server.env +WorkingDirectory=/opt/simdb-server + +# --- defaults (overridden by env file when set) ----------------------- +Environment="COMPOSE_FILE=docker-compose.yml:docker-compose.systemd.yml" +#Environment="SYSTEMD_CONTAINER_SIMDB=simdb-server-systemd" +#Environment="SIMDB_SERVER_TAG=latest" +#Environment="SIMDB_SERVER_IMAGE=ghcr.io/iterorganization/simdb" +#Environment="GUNICORN_BIND=0.0.0.0:5000" +#Environment="GUNICORN_WORKERS=3" +#Environment="GUNICORN_WORKER_CLASS=gthread" +#Environment="GUNICORN_THREADS=4" +#Environment="GUNICORN_TIMEOUT=120" + +# --- lifecycle -------------------------------------------------------- +ExecStartPre=-/usr/bin/docker compose --profile with_workers pull --ignore-pull-failures +ExecStart=/usr/bin/docker compose --profile with_workers up --detach --remove-orphans --no-build +ExecStop=/usr/bin/docker compose --profile with_workers down + +# --- restart behaviour ------------------------------------------------ +Restart=on-failure +RestartSec=10s + +# --- stop timeout ----------------------------------------------------- +TimeoutStopSec=30 + +# --- security hardening ----------------------------------------------- +NoNewPrivileges=yes +CapabilityBoundingSet=~CAP_SYS_ADMIN CAP_NET_ADMIN +ProtectSystem=strict +ProtectHome=yes +PrivateTmp=yes + +# --- logging ---------------------------------------------------------- +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target From 30c4d6e229400f55ee368de75eb97b5ebea6be28 Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Thu, 24 Sep 2026 12:01:12 +0200 Subject: [PATCH 26/30] systemd: add Makefile.systemd for easy deployment --- Makefile.systemd | 152 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 Makefile.systemd diff --git a/Makefile.systemd b/Makefile.systemd new file mode 100644 index 00000000..4411e3eb --- /dev/null +++ b/Makefile.systemd @@ -0,0 +1,152 @@ +SHELL := /bin/sh + +VERSION := $(shell git describe --tags --long --always 2>/dev/null | sed -r 's/-([0-9]+)/.dev\1/;s/-/+/' || echo 0.0.0) +PROJECT_NAME ?= simdb-server +COMPOSE_PROJECT_NAME ?= $(PROJECT_NAME) + +COMPOSE_FILE ?= docker-compose.yml:docker-compose.systemd.yml + +DOCKER_CMD ?= docker +DOCKER_BUILD ?= $(DOCKER_CMD) build --build-arg APP_VERSION="$(VERSION)" +DOCKER_COMPOSE ?= APP_VERSION="$(VERSION)" COMPOSE_FILE="$(COMPOSE_FILE)" $(DOCKER_CMD) compose + +# systemd-install destinations +package_optdir ?= /opt/$(PROJECT_NAME) +package_etcdir ?= /etc/$(PROJECT_NAME) +systemd_unitdir ?= /etc/systemd/system + +BUILD_IMAGE := simdb-server:build +SERVICE_IMAGE := simdb-server:service + +.DEFAULT_GOAL := service + +.PHONY: \ + build \ + builder \ + certs \ + dev \ + dist \ + distclean \ + down \ + help \ + lint \ + list \ + list-all \ + logs-f \ + service \ + shell \ + systemd-disable \ + systemd-daemon-reload \ + systemd-enable \ + systemd-installdirs \ + systemd-install \ + systemd-status \ + systemd-start \ + systemd-stop \ + systemd-uninstall \ + test \ + type-check \ + up \ + update-base \ + update-deps \ + version + +help: + @echo "Core workflow:" + @echo " make service Build and tag final service image" + @echo "" + @echo "Compose service (make service first):" + @echo " make up Start simdb-server service using prebuilt service image" + @echo " make down Stop simdb-server service" + @echo " make list List the web container for this compose project" + @echo " make list-all List all containers for this compose project" + @echo " make logs-f Follow logs of the started simdb-server service" + @echo " make shell Enter shell in the started simdb-server service" + @echo "" + @echo "Systemd integration (run with sudo; see docs/how-to/operate-server/install-server.md):" + @echo " sudo make systemd-install Copy files to $(package_optdir) and $(package_etcdir)" + @echo " sudo make systemd-enable systemctl daemon-reload && systemctl enable simdb-server" + @echo " sudo make systemd-start systemctl start simdb-server" + @echo " sudo make systemd-status systemctl status simdb-server" + @echo " sudo make systemd-stop systemctl stop simdb-server" + @echo " sudo make systemd-disable systemctl stop && systemctl disable simdb-server" + @echo " sudo make systemd-uninstall Remove files installed by systemd-install" + +service: + $(DOCKER_BUILD) --target service -t $(SERVICE_IMAGE) . + +# Compose targets +up: + $(DOCKER_COMPOSE) --profile with_workers up -d --no-build + +down: + $(DOCKER_COMPOSE) --profile with_workers down + +list: + $(DOCKER_CMD) ps \ + --filter "label=com.docker.compose.project=$(COMPOSE_PROJECT_NAME)" \ + --filter "label=com.docker.compose.service=web" + +list-all: + $(DOCKER_CMD) ps \ + --filter "label=com.docker.compose.project=$(COMPOSE_PROJECT_NAME)" + +logs-f: + $(DOCKER_COMPOSE) logs -f web + +shell: + $(DOCKER_COMPOSE) exec web sh + +# Systemd integration (run with sudo). +systemd-installdirs: + mkdir -p \ + $(DESTDIR)/$(package_optdir)/docker \ + $(DESTDIR)/$(package_optdir)/config \ + $(DESTDIR)/$(package_optdir)/validation \ + $(DESTDIR)/$(package_optdir)/upload_folder \ + $(DESTDIR)/$(package_optdir)/tmp/partition_data \ + $(DESTDIR)/$(package_etcdir) \ + $(DESTDIR)/$(systemd_unitdir) + +# NO NEED: +# - ./docker/gunicorn.conf.py:/app/docker/gunicorn.conf.py:ro +# NEED: +# - ./validation:/app/validation:ro +# - ./config:/app/config:ro +# - ./tmp/partition_data:/data/simdb/partition:ro +# - ./upload_folder:/data/simdb/simulations + +systemd-install: systemd-installdirs + install -m 644 \ + docker-compose.systemd.yml \ + docker-compose.yml \ + $(DESTDIR)/$(package_optdir) + install -m 644 \ + validation/iter_scenarios_validation.yaml \ + $(DESTDIR)/$(package_optdir)/validation + install -m 644 \ + config/simdb.cfg \ + $(DESTDIR)/$(package_optdir)/config + @if [ ! -f "$(DESTDIR)/$(package_etcdir)/simdb-server.env" ]; then \ + install -m 644 docker/scripts/simdb-server.env.example \ + "$(DESTDIR)/$(package_etcdir)/simdb-server.env"; \ + fi + install -m 644 \ + docker/scripts/simdb-server.service \ + $(DESTDIR)/$(systemd_unitdir)/simdb-server.service + +systemd-uninstall: systemd-disable + -rm -f --interactive $(DESTDIR)/$(systemd_unitdir)/simdb-server.service + -rm -rf --interactive $(DESTDIR)/$(package_etcdir) + -rm -rf --interactive $(DESTDIR)/$(package_optdir) + +systemd-daemon-reload: + systemctl daemon-reload + +systemd-enable: systemd-daemon-reload + +systemd-disable: systemd-stop + +systemd-start systemd-status systemd-stop systemd-enable systemd-disable: + -systemctl $(patsubst systemd-%,%,$@) simdb-server + From e198a3852fac7619738a2689e7fcfd94972d870d Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Thu, 24 Sep 2026 17:50:56 +0200 Subject: [PATCH 27/30] systemd: can run locally build image --- Makefile.systemd | 2 ++ docker/scripts/simdb-server.env.example | 3 +++ 2 files changed, 5 insertions(+) diff --git a/Makefile.systemd b/Makefile.systemd index 4411e3eb..91f3f2a8 100644 --- a/Makefile.systemd +++ b/Makefile.systemd @@ -57,6 +57,8 @@ help: @echo "" @echo "Compose service (make service first):" @echo " make up Start simdb-server service using prebuilt service image" + @echo " SIMDB_SERVER_IMAGE=simdb-server SIMDB_SERVER_TAG=service make up" + @echo " Start the locally built image created by make service" @echo " make down Stop simdb-server service" @echo " make list List the web container for this compose project" @echo " make list-all List all containers for this compose project" diff --git a/docker/scripts/simdb-server.env.example b/docker/scripts/simdb-server.env.example index c15b8b2a..8d378f4b 100644 --- a/docker/scripts/simdb-server.env.example +++ b/docker/scripts/simdb-server.env.example @@ -13,6 +13,9 @@ ## Choose image to use for the simdb-server container #SIMDB_SERVER_TAG=latest #SIMDB_SERVER_IMAGE=ghcr.io/iterorganization/simdb +# To run the locally built docker image (e.g. `make service`), use: +#SIMDB_SERVER_IMAGE=simdb-server +#SIMDB_SERVER_TAG=service # Binding: serve via TCP "0.0.0.0:5000" for Docker or network, #GUNICORN_BIND=0.0.0.0:5000 From 962323bba9061e98036f30e9c4a97b497dfcfab1 Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Wed, 30 Sep 2026 15:54:48 +0200 Subject: [PATCH 28/30] systemd: move Makefile, add docs --- Makefile | 161 +++++++++++++++++++ Makefile.systemd | 154 ------------------ docs/how-to/operate-server/install-server.md | 75 +++++++++ 3 files changed, 236 insertions(+), 154 deletions(-) create mode 100644 Makefile delete mode 100644 Makefile.systemd diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..39f5a32a --- /dev/null +++ b/Makefile @@ -0,0 +1,161 @@ +# Build and deploy the SimDB server as a systemd service. +# +# Two ways it is used: +# * `make service` / `make up` — build / run the compose stack locally. +# * `sudo make systemd-install` — install the compose files, config and unit +# file onto the host, then drive the service with `systemctl`. +# +# Variables can be overridden on the command line, e.g.: +# * Stage an installation under a prefix instead of installing under / +# (useful for packaging): make systemd-install DESTDIR=/tmp/simdb-staging +# * Run the locally built image (make service) instead of the published one +# (ghcr.io/iterorganization/simdb:latest): +# make up SIMDB_SERVER_IMAGE=simdb-server SIMDB_SERVER_TAG=service + +SHELL := /bin/sh + +# APP_VERSION is baked into the image; derive a PEP 440 version from git. +VERSION := $(shell git describe --tags --long --always 2>/dev/null | sed -E 's/-([0-9]+)/.dev\1/;s/-/+/' || echo 0.0.0) + +PROJECT_NAME ?= simdb-server +COMPOSE_PROJECT_NAME ?= $(PROJECT_NAME) + +# Base compose file first, then the systemd override on top. +COMPOSE_FILE ?= docker-compose.yml:docker-compose.systemd.yml + +DOCKER_CMD ?= docker +DOCKER_BUILD ?= $(DOCKER_CMD) build --build-arg APP_VERSION="$(VERSION)" +DOCKER_COMPOSE ?= APP_VERSION="$(VERSION)" COMPOSE_FILE="$(COMPOSE_FILE)" $(DOCKER_CMD) compose + +SERVICE_IMAGE := simdb-server:service + +# Destinations used by systemd-install/systemd-uninstall (DESTDIR for packaging). +package_optdir ?= /opt/$(PROJECT_NAME) +package_etcdir ?= /etc/$(PROJECT_NAME) +systemd_unitdir ?= /etc/systemd/system + +.DEFAULT_GOAL := service + +.PHONY: \ + down \ + help \ + list \ + list-all \ + logs-f \ + service \ + shell \ + systemd-daemon-reload \ + systemd-disable \ + systemd-enable \ + systemd-install \ + systemd-installdirs \ + systemd-start \ + systemd-status \ + systemd-stop \ + systemd-uninstall \ + up + +help: + @echo "Build and run:" + @echo " make service Build and tag the service image ($(SERVICE_IMAGE))" + @echo " make up Start the stack from the configured image" + @echo " make down Stop the stack" + @echo "" + @echo " make list List the web container for this compose project" + @echo " make list-all List all containers for this compose project" + @echo " make logs-f Follow the web container's logs" + @echo " make shell Open a shell in the web container" + @echo "" + @echo " SIMDB_SERVER_IMAGE=simdb-server SIMDB_SERVER_TAG=service make up" + @echo " Run the image built by 'make service' instead of the published one" + @echo "" + @echo "Systemd (run with sudo; see docs/how-to/operate-server/install-server.md):" + @echo " sudo make systemd-install Install files under $(package_optdir) and $(package_etcdir)" + @echo " sudo make systemd-enable systemctl daemon-reload && systemctl enable $(PROJECT_NAME)" + @echo " sudo make systemd-start systemctl start $(PROJECT_NAME)" + @echo " sudo make systemd-status systemctl status $(PROJECT_NAME)" + @echo " sudo make systemd-stop systemctl stop $(PROJECT_NAME)" + @echo " sudo make systemd-disable systemctl stop && systemctl disable $(PROJECT_NAME)" + @echo " sudo make systemd-uninstall Remove everything systemd-install created" + +service: + $(DOCKER_BUILD) --target service -t $(SERVICE_IMAGE) . + +# --- compose --------------------------------------------------------------- + +up: + $(DOCKER_COMPOSE) --profile with_workers up -d --no-build + +down: + $(DOCKER_COMPOSE) --profile with_workers down + +list: + $(DOCKER_CMD) ps \ + --filter "label=com.docker.compose.project=$(COMPOSE_PROJECT_NAME)" \ + --filter "label=com.docker.compose.service=web" + +list-all: + $(DOCKER_CMD) ps \ + --filter "label=com.docker.compose.project=$(COMPOSE_PROJECT_NAME)" + +logs-f: + $(DOCKER_COMPOSE) logs -f web + +shell: + $(DOCKER_COMPOSE) exec web sh + +# --- systemd integration (run with sudo) ----------------------------------- +# +# The service unit runs `docker compose` from $(package_optdir), which is why +# the compose files, the config and validation data are copied there rather +# than bind-mounted from the source tree. + +systemd-installdirs: + mkdir -p \ + $(DESTDIR)$(package_optdir)/config \ + $(DESTDIR)$(package_optdir)/validation \ + $(DESTDIR)$(package_optdir)/upload_folder \ + $(DESTDIR)$(package_optdir)/tmp/partition_data \ + $(DESTDIR)$(package_etcdir) \ + $(DESTDIR)$(systemd_unitdir) + +systemd-install: systemd-installdirs + install -m 644 \ + docker-compose.yml \ + docker-compose.systemd.yml \ + $(DESTDIR)$(package_optdir) + install -m 644 \ + config/simdb.cfg \ + $(DESTDIR)$(package_optdir)/config + install -m 644 \ + validation/iter_scenarios_validation.yaml \ + $(DESTDIR)$(package_optdir)/validation + install -m 644 \ + docker/scripts/simdb-server.service \ + $(DESTDIR)$(systemd_unitdir)/$(PROJECT_NAME).service + install -m 644 \ + docker/scripts/simdb-server.env.example \ + "$(DESTDIR)$(package_etcdir)/$(PROJECT_NAME).env.example" + @if [ ! -f "$(DESTDIR)$(package_etcdir)/$(PROJECT_NAME).env" ]; then \ + install -m 644 docker/scripts/simdb-server.env.example \ + "$(DESTDIR)$(package_etcdir)/$(PROJECT_NAME).env"; \ + fi + +systemd-uninstall: systemd-disable + @printf 'Remove $(DESTDIR)$(package_optdir) and $(DESTDIR)$(package_etcdir)? [y/N] '; \ + read ans; case "$$ans" in [yY]*) ;; *) echo "aborted"; exit 1;; esac + -rm -f "$(DESTDIR)$(systemd_unitdir)/$(PROJECT_NAME).service" + -rm -rf "$(DESTDIR)$(package_etcdir)" + -rm -rf "$(DESTDIR)$(package_optdir)" + +systemd-daemon-reload: + systemctl daemon-reload + +systemd-enable: systemd-daemon-reload + systemctl enable $(PROJECT_NAME) + +systemd-disable: systemd-stop + systemctl disable $(PROJECT_NAME) + +systemd-start systemd-status systemd-stop: + -systemctl $(patsubst systemd-%,%,$@) $(PROJECT_NAME) diff --git a/Makefile.systemd b/Makefile.systemd deleted file mode 100644 index 91f3f2a8..00000000 --- a/Makefile.systemd +++ /dev/null @@ -1,154 +0,0 @@ -SHELL := /bin/sh - -VERSION := $(shell git describe --tags --long --always 2>/dev/null | sed -r 's/-([0-9]+)/.dev\1/;s/-/+/' || echo 0.0.0) -PROJECT_NAME ?= simdb-server -COMPOSE_PROJECT_NAME ?= $(PROJECT_NAME) - -COMPOSE_FILE ?= docker-compose.yml:docker-compose.systemd.yml - -DOCKER_CMD ?= docker -DOCKER_BUILD ?= $(DOCKER_CMD) build --build-arg APP_VERSION="$(VERSION)" -DOCKER_COMPOSE ?= APP_VERSION="$(VERSION)" COMPOSE_FILE="$(COMPOSE_FILE)" $(DOCKER_CMD) compose - -# systemd-install destinations -package_optdir ?= /opt/$(PROJECT_NAME) -package_etcdir ?= /etc/$(PROJECT_NAME) -systemd_unitdir ?= /etc/systemd/system - -BUILD_IMAGE := simdb-server:build -SERVICE_IMAGE := simdb-server:service - -.DEFAULT_GOAL := service - -.PHONY: \ - build \ - builder \ - certs \ - dev \ - dist \ - distclean \ - down \ - help \ - lint \ - list \ - list-all \ - logs-f \ - service \ - shell \ - systemd-disable \ - systemd-daemon-reload \ - systemd-enable \ - systemd-installdirs \ - systemd-install \ - systemd-status \ - systemd-start \ - systemd-stop \ - systemd-uninstall \ - test \ - type-check \ - up \ - update-base \ - update-deps \ - version - -help: - @echo "Core workflow:" - @echo " make service Build and tag final service image" - @echo "" - @echo "Compose service (make service first):" - @echo " make up Start simdb-server service using prebuilt service image" - @echo " SIMDB_SERVER_IMAGE=simdb-server SIMDB_SERVER_TAG=service make up" - @echo " Start the locally built image created by make service" - @echo " make down Stop simdb-server service" - @echo " make list List the web container for this compose project" - @echo " make list-all List all containers for this compose project" - @echo " make logs-f Follow logs of the started simdb-server service" - @echo " make shell Enter shell in the started simdb-server service" - @echo "" - @echo "Systemd integration (run with sudo; see docs/how-to/operate-server/install-server.md):" - @echo " sudo make systemd-install Copy files to $(package_optdir) and $(package_etcdir)" - @echo " sudo make systemd-enable systemctl daemon-reload && systemctl enable simdb-server" - @echo " sudo make systemd-start systemctl start simdb-server" - @echo " sudo make systemd-status systemctl status simdb-server" - @echo " sudo make systemd-stop systemctl stop simdb-server" - @echo " sudo make systemd-disable systemctl stop && systemctl disable simdb-server" - @echo " sudo make systemd-uninstall Remove files installed by systemd-install" - -service: - $(DOCKER_BUILD) --target service -t $(SERVICE_IMAGE) . - -# Compose targets -up: - $(DOCKER_COMPOSE) --profile with_workers up -d --no-build - -down: - $(DOCKER_COMPOSE) --profile with_workers down - -list: - $(DOCKER_CMD) ps \ - --filter "label=com.docker.compose.project=$(COMPOSE_PROJECT_NAME)" \ - --filter "label=com.docker.compose.service=web" - -list-all: - $(DOCKER_CMD) ps \ - --filter "label=com.docker.compose.project=$(COMPOSE_PROJECT_NAME)" - -logs-f: - $(DOCKER_COMPOSE) logs -f web - -shell: - $(DOCKER_COMPOSE) exec web sh - -# Systemd integration (run with sudo). -systemd-installdirs: - mkdir -p \ - $(DESTDIR)/$(package_optdir)/docker \ - $(DESTDIR)/$(package_optdir)/config \ - $(DESTDIR)/$(package_optdir)/validation \ - $(DESTDIR)/$(package_optdir)/upload_folder \ - $(DESTDIR)/$(package_optdir)/tmp/partition_data \ - $(DESTDIR)/$(package_etcdir) \ - $(DESTDIR)/$(systemd_unitdir) - -# NO NEED: -# - ./docker/gunicorn.conf.py:/app/docker/gunicorn.conf.py:ro -# NEED: -# - ./validation:/app/validation:ro -# - ./config:/app/config:ro -# - ./tmp/partition_data:/data/simdb/partition:ro -# - ./upload_folder:/data/simdb/simulations - -systemd-install: systemd-installdirs - install -m 644 \ - docker-compose.systemd.yml \ - docker-compose.yml \ - $(DESTDIR)/$(package_optdir) - install -m 644 \ - validation/iter_scenarios_validation.yaml \ - $(DESTDIR)/$(package_optdir)/validation - install -m 644 \ - config/simdb.cfg \ - $(DESTDIR)/$(package_optdir)/config - @if [ ! -f "$(DESTDIR)/$(package_etcdir)/simdb-server.env" ]; then \ - install -m 644 docker/scripts/simdb-server.env.example \ - "$(DESTDIR)/$(package_etcdir)/simdb-server.env"; \ - fi - install -m 644 \ - docker/scripts/simdb-server.service \ - $(DESTDIR)/$(systemd_unitdir)/simdb-server.service - -systemd-uninstall: systemd-disable - -rm -f --interactive $(DESTDIR)/$(systemd_unitdir)/simdb-server.service - -rm -rf --interactive $(DESTDIR)/$(package_etcdir) - -rm -rf --interactive $(DESTDIR)/$(package_optdir) - -systemd-daemon-reload: - systemctl daemon-reload - -systemd-enable: systemd-daemon-reload - -systemd-disable: systemd-stop - -systemd-start systemd-status systemd-stop systemd-enable systemd-disable: - -systemctl $(patsubst systemd-%,%,$@) simdb-server - diff --git a/docs/how-to/operate-server/install-server.md b/docs/how-to/operate-server/install-server.md index 96eb2fb4..652bb4e7 100644 --- a/docs/how-to/operate-server/install-server.md +++ b/docs/how-to/operate-server/install-server.md @@ -78,9 +78,84 @@ the [Docker Compose deployment](run-with-docker.md) instead of installing by hand. ``` +## Deploy as a systemd service + +The `Makefile` at the repository root automates the usual Docker Compose +deployment as a systemd unit: it installs the Compose files, the server +configuration and validation data under `/opt/simdb-server`, writes the unit +file and environment file, and wraps the `systemctl` calls. Run it from the +repository root, through `sudo` for anything that touches the system: + +```bash +sudo make systemd-install # copy files into place +sudo make systemd-enable # daemon-reload and enable at boot +sudo make systemd-start # start the service +``` + +Day-to-day control: + +```bash +sudo make systemd-status +sudo make systemd-stop +sudo make systemd-disable # stop and disable at boot +sudo make systemd-uninstall # stop and remove installed files +``` + +`make help` prints the same list, and the Compose targets +(`up`, `down`, `logs-f`, `shell`, …) are available for running the stack without +installing a service. See +[Run with Docker Compose](run-with-docker.md) for what the stack contains. + +### What gets installed + +| Path | Contents | +| --- | --- | +| `/opt/simdb-server` | Compose files, `config/simdb.cfg`, and validation data | +| `/etc/simdb-server/simdb-server.env` | Environment file read by the unit (`docker/scripts/simdb-server.env.example` on first install) | +| `/etc/systemd/system/simdb-server.service` | The systemd unit | + +The unit starts the stack from the installed Compose files, so the source +checkout is no longer needed once `systemd-install` has run. Database and Redis +state persist in the Compose named volumes and survives an uninstall. + +### Configure + +Edit `/etc/simdb-server/simdb-server.env` before starting the service. It +selects the image and sets the Gunicorn runtime options; see +[docker/scripts/simdb-server.env.example](../../../docker/scripts/simdb-server.env.example) +for every variable, commented out with its default. + +By default the service runs the published image +(`ghcr.io/iterorganization/simdb:latest`). To run an image built from your +checkout instead, build it and point the environment file at it: + +```bash +make service +``` + +```bash +# /etc/simdb-server/simdb-server.env +SIMDB_SERVER_IMAGE=simdb-server +SIMDB_SERVER_TAG=service +``` + +The server itself is configured through the installed +`/opt/simdb-server/config/simdb.cfg`, exactly as described in +[Run with Docker Compose](run-with-docker.md#configure). Installation paths and +the Compose project name can be overridden on the command line, for example +`sudo make systemd-install package_optdir=/srv/simdb`. + +```{tip} +To stage an installation without touching `/` — for packaging, or to inspect +what would be written — pass a prefix: `make systemd-install +DESTDIR=/tmp/simdb-staging`. +``` + ## Next steps - [Run with Docker Compose](run-with-docker.md) for an all-in-one deployment. +- [Deploy as a systemd service](#deploy-as-a-systemd-service) to keep it running + on a server. - [Set up PostgreSQL](set-up-postgresql.md) for production. - [Configure authentication](configure-authentication.md). - [Configure validation](configure-validation.md). From 85be298dd5b739712d9b9e53824e950367fdc652 Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Wed, 30 Sep 2026 18:10:19 +0200 Subject: [PATCH 29/30] docker: do not mount gunicorn.conf.py No need: it is baked into the image and reads everything tunable from the GUNICORN_* environment. Plus, since the file is not installed via make systemd-install, this volume mount of a missing file actually breaks docker compose up. --- docker-compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 8665b7e2..bfbb7c65 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,7 +32,6 @@ services: ports: - "5000:5000" volumes: - - ./docker/gunicorn.conf.py:/app/docker/gunicorn.conf.py:ro - ./validation:/app/validation:ro - ./config:/app/config:ro - ./tmp/partition_data:/data/simdb/partition:ro From 23a04243f6bd16110040a9d59985d4a6b123e753 Mon Sep 17 00:00:00 2001 From: Louwrens van Dellen Date: Thu, 1 Oct 2026 16:39:05 +0200 Subject: [PATCH 30/30] docker, ci and docs: make consistent, simplify and trim unused code remove docker test target to verify code, simplify gha workflow to reduce artifact juggling, consistent image addresses, consistent git/image/package version tags, simple ci triggers for image building tagging and publishing, documentation errata. --- .github/workflows/docker_image.yml | 131 +++++++------------ Dockerfile | 31 ----- Makefile | 2 +- docker-compose.systemd.yml | 4 +- docker/scripts/simdb-server.env.example | 2 +- docker/scripts/simdb-server.service | 2 +- docs/how-to/operate-server/install-server.md | 22 +++- docs/how-to/use-the-dashboard.md | 5 +- pyproject.toml | 2 +- 9 files changed, 79 insertions(+), 122 deletions(-) diff --git a/.github/workflows/docker_image.yml b/.github/workflows/docker_image.yml index ae7b8cae..4bf0d67f 100644 --- a/.github/workflows/docker_image.yml +++ b/.github/workflows/docker_image.yml @@ -1,26 +1,36 @@ name: Docker Image build and publish +# Triggers and action summary: +# pull-request -> ignored in this GHA, linting and testing done elsewhere. +# push develop -> build and push image tagged : and :develop. +# push main -> build and push image tagged :latest. +# push tag -> build and push image tagged :. + env: CACHE_FROM: type=gha,scope=simdb-server CACHE_TO: type=gha,mode=max,scope=simdb-server SETUPTOOLS_SCM_OVERRIDES_FOR_IMAS_SIMDB: '{local_scheme = "no-local-version-strict"}' on: - # Triggers on pushes to develop, on pushed tags, and on pull requests targeting develop. push: - branches: [ "develop" ] + branches: [ "develop", "main" ] tags: [ "*" ] - pull_request: - branches: [ "develop" ] + +# Serialize runs per ref, so that two merges in quick succession can't race to +# move the "develop" image tag backward. The running job is never cancelled; +# GitHub does however keep only the newest *pending* run per group, so a third +# queued run supersedes the second. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false jobs: - build: - name: Build, verify, and package + build-and-publish: + name: Build and publish runs-on: ubuntu-latest - outputs: - version: ${{ steps.version.outputs.value }} permissions: contents: read + packages: write steps: - name: Checkout code uses: actions/checkout@v7 @@ -28,9 +38,19 @@ jobs: fetch-tags: true fetch-depth: 0 - - name: Get package version + - name: Get version id: version - run: echo "value=$(git describe --tags --long --always | sed -r 's/-([0-9]+)/.dev\1/;s/-/+/')" >> "$GITHUB_OUTPUT" + run: | + # A commit between tags describes as "--g", turned into the + # PEP 440 version ".dev+g"; a commit on a tag describes as + # just "" and is used as is. + VERSION=$(git describe --tags --always | sed -r 's/-([0-9]+)-g([0-9a-f]+)$/.dev\1+g\2/') + # A Docker tag allows only [A-Za-z0-9_.-], so drop the '+' local segment + # for the image tag. That is the version setuptools-scm bakes into the + # image (local_scheme = no-local-version-strict), so the tag and + # "simdb --version" agree. + echo "value=$VERSION" >> "$GITHUB_OUTPUT" + echo "tag_value=${VERSION%%+*}" >> "$GITHUB_OUTPUT" - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 @@ -45,16 +65,6 @@ jobs: cache-from: ${{ env.CACHE_FROM }} cache-to: ${{ env.CACHE_TO }} - - name: Build validation image - uses: docker/build-push-action@v7 - with: - target: test - load: true - tags: simdb-server:test - build-args: APP_VERSION=${{ steps.version.outputs.value }} - cache-from: ${{ env.CACHE_FROM }} - cache-to: ${{ env.CACHE_TO }} - - name: Build service image uses: docker/build-push-action@v7 with: @@ -65,62 +75,10 @@ jobs: cache-from: ${{ env.CACHE_FROM }} cache-to: ${{ env.CACHE_TO }} - - name: Export service image tar - run: docker save -o "${{ runner.temp }}/simdb-server-service.tar" simdb-server:service - - - name: Upload service image artifact - uses: actions/upload-artifact@v7 - with: - name: simdb-server-service - path: ${{ runner.temp }}/simdb-server-service.tar - retention-days: 7 - - verify: - name: Verify Docker validation stage - runs-on: ubuntu-latest - needs: build - permissions: - contents: read - steps: - - name: Checkout code - uses: actions/checkout@v7 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 - - - name: Build validation stage only - uses: docker/build-push-action@v7 - with: - target: test - load: true - tags: simdb-server:test - build-args: APP_VERSION=0.0.0 - cache-from: ${{ env.CACHE_FROM }} - cache-to: ${{ env.CACHE_TO }} - - publish: - name: Publish service image - # Runs only for push events (branch/tag pushes), and is skipped for pull_request events. - if: ${{ github.event_name == 'push' }} - runs-on: ubuntu-latest - needs: build - permissions: - contents: write - packages: write - steps: - name: Lowercase repo-owner id: repo_owner run: echo "value=${GITHUB_REPOSITORY_OWNER,,}" >> "$GITHUB_OUTPUT" - - name: Download service image artifact - uses: actions/download-artifact@v8 - with: - name: simdb-server-service - path: ${{ runner.temp }} - - - name: Load service image - run: docker load -i "${{ runner.temp }}/simdb-server.tar" - - name: Login to GitHub Container Registry uses: docker/login-action@v4 with: @@ -128,21 +86,30 @@ jobs: username: ${{ steps.repo_owner.outputs.value }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Tag and push service image with versioned tag and "nightly" - if: ${{ github.ref_type == 'branch' }} + # Versioned tag: develop pushes and any tag push. main is excluded -- it + # follows develop and only publishes :latest. + - name: Tag and push service image with version tag + if: ${{ github.ref_type == 'tag' || github.ref_name == 'develop' }} run: | - docker tag simdb-server:service ghcr.io/${{ steps.repo_owner.outputs.value }}/simdb-server:${{ needs.build.outputs.version }} - docker tag simdb-server:service ghcr.io/${{ steps.repo_owner.outputs.value }}/simdb-server:nightly - docker push ghcr.io/${{ steps.repo_owner.outputs.value }}/simdb-server:${{ needs.build.outputs.version }} - docker push ghcr.io/${{ steps.repo_owner.outputs.value }}/simdb-server:nightly + docker tag \ + simdb-server:service \ + ghcr.io/${{ steps.repo_owner.outputs.value }}/simdb-server:${{ steps.version.outputs.tag_value }} + docker push ghcr.io/${{ steps.repo_owner.outputs.value }}/simdb-server:${{ steps.version.outputs.tag_value }} + # Run on develop pushes only + - name: Tag image :develop and publish + if: ${{ github.ref_type == 'branch' && github.ref_name == 'develop' }} + run: | + docker tag \ + simdb-server:service \ + ghcr.io/${{ steps.repo_owner.outputs.value }}/simdb-server:develop + docker push ghcr.io/${{ steps.repo_owner.outputs.value }}/simdb-server:develop - # These steps run on tagged commits only - - name: Tag and push latest - if: ${{ github.ref_type == 'tag' }} + # Run on main pushes only + - name: Tag image :latest and publish + if: ${{ github.ref_type == 'branch' && github.ref_name == 'main' }} run: | docker tag \ simdb-server:service \ ghcr.io/${{ steps.repo_owner.outputs.value }}/simdb-server:latest docker push ghcr.io/${{ steps.repo_owner.outputs.value }}/simdb-server:latest - diff --git a/Dockerfile b/Dockerfile index 9c11ed25..17924169 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,37 +31,6 @@ RUN uv sync --locked --no-dev --extra all ENV SIMDB_SITE_CONFIG_PATH=/app/config/simdb.cfg -# Validation stage: run linting and tests using the project dev environment. -FROM ghcr.io/astral-sh/uv:0.12.17-python3.12-trixie-slim@sha256:9a59bb7206905ccaae4f7dab222fbac47c125a21e5fc16f43f427cd6c940ade3 \ - AS test - -ENV UV_LINK_MODE=copy \ - UV_COMPILE_BYTECODE=1 \ - PYTHONUNBUFFERED=1 - -WORKDIR /app - -RUN apt-get update && apt-get install -y --no-install-recommends \ - build-essential \ - libpq-dev \ - libldap2-dev \ - libsasl2-dev \ - libmagic1 \ - && rm -rf /var/lib/apt/lists/* - -COPY uv.lock pyproject.toml ./ -COPY alembic.ini ./ -COPY docker/gunicorn.conf.py ./docker/gunicorn.conf.py -COPY src/ ./src/ -COPY tests/ ./tests/ - -RUN uv sync --locked --group dev --extra all - -RUN uv run python -m ruff format --check -RUN uv run python -m ruff check -RUN uv run python -m ty check src -RUN uv run python -m pytest --cov=simdb --cov-report=term-missing --cov-report=xml:coverage.xml --cov-report=html:htmlcov -v --tb=short - # Runtime stage: Minimal image with only runtime dependencies FROM ghcr.io/astral-sh/uv:0.12.17-python3.12-trixie-slim@sha256:9a59bb7206905ccaae4f7dab222fbac47c125a21e5fc16f43f427cd6c940ade3 \ AS service diff --git a/Makefile b/Makefile index 39f5a32a..403fe3bc 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ # * Stage an installation under a prefix instead of installing under / # (useful for packaging): make systemd-install DESTDIR=/tmp/simdb-staging # * Run the locally built image (make service) instead of the published one -# (ghcr.io/iterorganization/simdb:latest): +# (ghcr.io/iterorganization/simdb-server:latest): # make up SIMDB_SERVER_IMAGE=simdb-server SIMDB_SERVER_TAG=service SHELL := /bin/sh diff --git a/docker-compose.systemd.yml b/docker-compose.systemd.yml index ddf270ed..224be256 100644 --- a/docker-compose.systemd.yml +++ b/docker-compose.systemd.yml @@ -2,7 +2,7 @@ # # Uses the GHCR-published image instead of a local build. # -# Set SIMDB_DASHBOARD_TAG in the environment file to pin a specific +# Set SIMDB_SERVER_TAG in the environment file to pin a specific # version. The default is the latest tag at the time of writing. # # Enable this override by including it in COMPOSE_FILE, @@ -14,7 +14,7 @@ services: # Systemd deployment should only manage a single container instance, docker requires # container_name to be unique, so use it to ensure a fixed name. container_name: ${SYSTEMD_CONTAINER_SIMDB:-simdb-server-systemd} - image: ${SIMDB_SERVER_IMAGE:-ghcr.io/iterorganization/simdb}:${SIMDB_SERVER_TAG:-latest} + image: ${SIMDB_SERVER_IMAGE:-ghcr.io/iterorganization/simdb-server}:${SIMDB_SERVER_TAG:-latest} logging: driver: journald options: diff --git a/docker/scripts/simdb-server.env.example b/docker/scripts/simdb-server.env.example index 8d378f4b..3025827a 100644 --- a/docker/scripts/simdb-server.env.example +++ b/docker/scripts/simdb-server.env.example @@ -12,7 +12,7 @@ ## Choose image to use for the simdb-server container #SIMDB_SERVER_TAG=latest -#SIMDB_SERVER_IMAGE=ghcr.io/iterorganization/simdb +#SIMDB_SERVER_IMAGE=ghcr.io/iterorganization/simdb-server # To run the locally built docker image (e.g. `make service`), use: #SIMDB_SERVER_IMAGE=simdb-server #SIMDB_SERVER_TAG=service diff --git a/docker/scripts/simdb-server.service b/docker/scripts/simdb-server.service index b924cf73..22ea6d63 100644 --- a/docker/scripts/simdb-server.service +++ b/docker/scripts/simdb-server.service @@ -20,7 +20,7 @@ WorkingDirectory=/opt/simdb-server Environment="COMPOSE_FILE=docker-compose.yml:docker-compose.systemd.yml" #Environment="SYSTEMD_CONTAINER_SIMDB=simdb-server-systemd" #Environment="SIMDB_SERVER_TAG=latest" -#Environment="SIMDB_SERVER_IMAGE=ghcr.io/iterorganization/simdb" +#Environment="SIMDB_SERVER_IMAGE=ghcr.io/iterorganization/simdb-server" #Environment="GUNICORN_BIND=0.0.0.0:5000" #Environment="GUNICORN_WORKERS=3" #Environment="GUNICORN_WORKER_CLASS=gthread" diff --git a/docs/how-to/operate-server/install-server.md b/docs/how-to/operate-server/install-server.md index 652bb4e7..828ef57a 100644 --- a/docs/how-to/operate-server/install-server.md +++ b/docs/how-to/operate-server/install-server.md @@ -126,7 +126,7 @@ selects the image and sets the Gunicorn runtime options; see for every variable, commented out with its default. By default the service runs the published image -(`ghcr.io/iterorganization/simdb:latest`). To run an image built from your +(`ghcr.io/iterorganization/simdb-server:latest`). To run an image built from your checkout instead, build it and point the environment file at it: ```bash @@ -139,6 +139,26 @@ SIMDB_SERVER_IMAGE=simdb-server SIMDB_SERVER_TAG=service ``` +Images are published to `ghcr.io/iterorganization/simdb-server` by the +[Docker Image build and publish](https://github.com/iterorganization/SimDB/blob/develop/.github/workflows/docker_image.yml) +workflow: + +| Tag | Published on | +| --- | --- | +| `latest` | every push to `main` | +| `develop` | every push to `develop` | +| `` | every push to `develop`, and every tag push | + +`` is the version reported by `simdb --version` inside that image: the +tag name itself for a release (for example `0.15.2`), or a development version +such as `0.15.2.dev319` for a build between releases. In production, pin it +rather than tracking a moving tag: + +```bash +# /etc/simdb-server/simdb-server.env +SIMDB_SERVER_TAG=0.15.2 +``` + The server itself is configured through the installed `/opt/simdb-server/config/simdb.cfg`, exactly as described in [Run with Docker Compose](run-with-docker.md#configure). Installation paths and diff --git a/docs/how-to/use-the-dashboard.md b/docs/how-to/use-the-dashboard.md index 5b7c549b..d9c9da85 100644 --- a/docs/how-to/use-the-dashboard.md +++ b/docs/how-to/use-the-dashboard.md @@ -1,7 +1,8 @@ # Use the dashboard -Besides the CLI, a SimDB server has a web dashboard for browsing simulation -metadata in the browser. +Apart from using the SimDB CLI, a SimDB server can be explored via +[SimDB-Dashboard](https://github.com/iterorganization/SimDB-Dashboard) for browsing +simulation metadata in the browser. ## Open a simulation by UUID diff --git a/pyproject.toml b/pyproject.toml index 46460a95..3fcf96a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,7 +101,7 @@ simdb_worker = "simdb.workers.cli:worker" simdb_beat = "simdb.workers.cli:beat" [project.urls] -Homepage = "https://simdb.iter.org/dashboard/" +Homepage = "https://simdb.iter.org/" Documentation = "https://simdb.readthedocs.io/en/latest/" Repository = "https://github.com/iterorganization/SimDB"