Skip to content

Conversation

@davidbeig
Copy link
Contributor

@davidbeig davidbeig commented Oct 16, 2025

This PR closes #113 .

We are adding a script that enables the creation of a Decidim instance based on the docker images built in this repository. The user will be guided through some questions on how to properly configure the instance, and the script itself will take care of the necessary dependencies (such as the database, where the storage is going to be located, etc).

Some improvements:

  • Checksum every file.
    This will generate extra-security on the download of the shell script and the files it uses, giving the final user more security.
  • Add health capabilities to the containers.

What we still need

  • We need a way to deliver releases of the install script. Currently there's a github action to create a .zip file that the script is going to download in the server and build from there all the infrastructure.

How to test

Using the released zip.

sudo curl -s https://raw.githubusercontent.com/decidim/docker/refs/heads/feat/decidim_install/install/install.sh | bash

While developing we might not have access to the released zip, the way to test it is to zip ourselfs the script folder and push it to the server.

cd install
zip -r deploy.zip *
scp deploy.zip user@ip:/tmp/decidim-docker/install

Later on, we need the deploy.zip to be under the /tmp/decidim-docker-files/deploy.zip. It's a bit messy right now.

Also, if you want to try it locally you can use multipass

cd install
zip -r deploy.zip 
cd ..
multipass launch --name decidim-docker --cpus 2 --memory 4G --disk 20G
multipass mount . decidim-docker:/home/ubuntu/decidim-docker
multipass shell decidim-docker
cd decidim-docker
bash install/install.sh

Summary by CodeRabbit

  • New Features

    • Adds automated build-and-release workflow that produces a deployable ZIP.
    • Provides a full containerized deployment setup (app, worker, proxy, DB, cache) and orchestrated startup.
    • Adds an interactive installer and environment builder with guided prompts and key generation.
    • Adds scripts to verify OS/Docker, open firewall ports, and create an admin user.
  • Documentation

    • New production deployment guide with install steps, prerequisites, component overview, and environment variable reference.
  • Refactor

    • Replaces an older container entrypoint with updated startup entrypoints and Sidekiq setup.

✏️ Tip: You can customize this high-level summary in your review settings.

@davidbeig davidbeig self-assigned this Oct 16, 2025
Copy link

@greenwoodt greenwoodt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some standard but very minor changes to wording of script language communicating to the implementer during the installation. Also adding consistency to what you've already done.

Take a long and let me know what you think @davidbeig :)

All in all really beautifully described!

@coderabbitai
Copy link

coderabbitai bot commented Jan 22, 2026

📝 Walkthrough

Walkthrough

Adds a production installer and supporting scripts, a docker-compose multi-service deployment (app, worker, traefik, db, cache), Sidekiq config and container entrypoints, extensive deployment documentation, and a GitHub Actions workflow that packages and publishes the install/ folder as a release ZIP.

Changes

Cohort / File(s) Summary
CI / Release
\.github/workflows/build_install_folder.yml
New GitHub Actions workflow "Build Deployment Zip" that packages the install/ folder into deploy_bundle.zip and publishes it via GitHub Releases.
Top-level installer & orchestration
install/install.sh, install/up.sh
New installer install.sh (downloads/extracts release ZIP, sequences dependency scripts) and up.sh (validates .env and runs docker compose up -d, shows recent logs).
Docker compose & runtime
install/docker-compose.yml, install/config/sidekiq.yml
New docker-compose.yml defining services app, worker (Sidekiq), traefik, db (Postgres), cache (Redis) and persistent volumes; new Sidekiq YAML with concurrency and weighted queues.
Container entrypoints
install/scripts/entrypoint.sh, install/scripts/sidekiq_entrypoint.sh
Entrypoints that adjust UID/GID, ensure gems installed, conditionally run migrations, and exec the final command for app and Sidekiq worker.
Dependency & interactive tooling
install/dependencies/*
install/dependencies/os_version.sh, check_docker.sh, decidim_version.sh, open_ports.sh, build_env.sh, generate_gemfile.sh, generate_vapid_keys.sh, create_system_admin.sh
New modular scripts: OS validation, Docker check/install, image pulling, UFW port opening, interactive .env builder (DB, SMTP, storage, VAPID, maps), Gemfile templates, VAPID generation via Docker, and automated system admin creation with health checks.
Removed legacy script
scripts/entrypoint.sh
Removed previous host entrypoint script (replaced by install/scripts/*).
Docs & README
README.md, install/README.md
Extended top-level README with "Using a production deploy script" and env reference; new install/README.md with full installation guide and troubleshooting.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant GH_Releases as "GitHub Releases"
  participant Installer as "install/install.sh"
  participant Host as "Server (Docker)"
  participant Compose as "docker-compose"
  participant Traefik as "Traefik"
  participant App as "Decidim App"
  participant Worker as "Sidekiq"
  participant DB as "Postgres"
  participant Cache as "Redis"

  User->>Installer: run installer (curl ... | bash) / trigger workflow
  Installer->>GH_Releases: download `deploy_bundle.zip`
  GH_Releases-->>Installer: return ZIP
  Installer->>Host: extract files into REPOSITORY_PATH (/opt/decidim)
  Installer->>Host: run dependencies/*.sh (os_version, check_docker, open_ports, build_env...)
  Installer->>Host: generate VAPID, Gemfiles, create .env
  Installer->>Compose: docker compose --env-file .env up -d
  Compose->>DB: start postgres container
  Compose->>Cache: start redis container
  Compose->>Traefik: start reverse proxy
  Compose->>App: start app container
  Compose->>Worker: start sidekiq container
  App->>DB: connect and run migrations
  Worker->>DB: connect
  Traefik->>App: route external requests
  Installer->>Host: run create_system_admin.sh (rails runner) -> returns admin credentials
  Host-->>User: installation complete, admin credentials displayed
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I dug a script and found a zip,
Servers hummed and containers skip,
Env keys tucked safe on my lap,
Sidekiq dances, traefik maps,
Admin hopped in — deploy complete! 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Create install.sh script' accurately describes the main addition: a new installation script file, which is the core deliverable of this PR.
Linked Issues check ✅ Passed The PR implements the core requirements from issue #113: OS detection (Debian/Ubuntu), Docker checking/installation, interactive configuration collection (app name, admin, host URL, SMTP), Traefik reverse proxy with SSL, Sidekiq/Redis setup, and comprehensive installation documentation.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the installation objectives: helper scripts, configuration files, Docker setup, documentation, and the main install script. No unrelated refactoring or feature additions detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

🤖 Fix all issues with AI agents
In @.github/workflows/build_install_folder.yml:
- Around line 16-20: The "Create deployment ZIP" workflow step is zipping a
non-existent scripts/ directory; update the zip command that produces
deploy_bundle.zip to include the correct installer path (e.g., install/ or
install/scripts/) so the actual installer files are packaged (modify the zip
source arguments in the step named "Create deployment ZIP" to reference install/
instead of scripts/).
- Around line 3-7: The release step using softprops/action-gh-release is
vulnerable because tag_name defaults to github.ref and the workflow is triggered
on branch pushes; either restrict the workflow trigger to tag pushes (e.g.,
change on: push to push: tags: ['v*']) or add a conditional on the release
job/step (use if: startsWith(github.ref, 'refs/tags/')) so the
softprops/action-gh-release step (the step invoking softprops/action-gh-release
and setting tag_name) only runs for tag refs.

In `@install/dependencies/build_env.sh`:
- Around line 114-126: The script fails under set -u when choosing the external
DB because COMPOSE_PROFILES is only defined in build_local_database; initialize
a safe default before it's referenced. Modify the top-level flow near the
EXTERNAL_DATABASE logic so that COMPOSE_PROFILES is exported with a default
empty value (or appropriate default profile) when EXTERNAL_DATABASE=true, and
ensure build_external_database and build_local_database do not assume
COMPOSE_PROFILES is already set; update any places that expand COMPOSE_PROFILES
to use a safe expansion pattern or rely on the newly exported default. Reference
the existing symbols build_external_database, build_local_database,
EXTERNAL_DATABASE, and COMPOSE_PROFILES when making the change.
- Around line 191-193: The script has a typo and inconsistent variable names:
correct MAPS_API_PROIVDER to MAPS_API_PROVIDER wherever present and ensure the
final exported/used variable matches what docker-compose expects
(MAPS_PROVIDER). Update the assignments so MAPS_PROVIDER is set from the
(correctly spelled) MAPS_API_PROVIDER (or replace usages to consistently use
MAPS_PROVIDER), and fix the same typo occurrences later in the file (the
repeated MAPS_API_PROIVDER instances) so the container receives the intended
provider value.

In `@install/dependencies/check_docker.sh`:
- Around line 12-15: Replace the direct pipe of the remote script to bash with a
two-step download-and-execute flow: change the curl | bash usage so the script
is first fetched to a temporary file (the code that currently uses the curl ->
bash pipeline in install/dependencies/check_docker.sh), allow for inspection or
checksum/GPG validation, then explicitly execute the saved file with bash and
remove it afterwards; ensure failure of either download or execution returns
non-zero and prints a clear error message (the same "Failed to install Docker"
handling should remain but triggered after validating and running the downloaded
script).

In `@install/dependencies/create_system_admin.sh`:
- Around line 15-21: The if-test references EXTERNAL_DATABASE and will fail
under set -u if the variable is unset; update the condition in that block to use
shell parameter expansion to provide a safe default (use the ":-" expansion to
default EXTERNAL_DATABASE to an empty string) when testing for emptiness so the
script won't crash under set -u — adjust the if [ -z ... ] check accordingly
around the EXTERNAL_DATABASE usage.
- Around line 31-45: The current flow calls generate_system_admin with set -e
enabled so the script will exit on failure and never reach the if [ $? -eq 1 ]
branch; move the generate_system_admin invocation into the conditional test
instead (e.g., run it as part of the if statement) so you can test its exit
status and handle both success and failure paths, updating the existing branches
that reference SYSTEM_PASSWORD and DECIDIM_DOMAIN accordingly and keeping the
same log messages for success and the troubleshooting block for failures.

In `@install/dependencies/decidim_version.sh`:
- Around line 15-27: The loop currently keeps retrying the same DECIDIM_IMAGE
forever; after a failed docker pull (inside the while-true loop around docker
pull "$DECIDIM_IMAGE") prompt the user to optionally enter a new image name (or
press Enter to retry the same), read that input into DECIDIM_IMAGE (falling back
to the previous value if empty), and then continue the loop so the next docker
pull uses the updated DECIDIM_IMAGE; make sure to reference the DECIDIM_IMAGE
variable and the docker pull retry block when applying the change.

In `@install/dependencies/generate_vapid_keys.sh`:
- Around line 21-28: The script currently exports VAPID_PUBLIC_KEY and
VAPID_PRIVATE_KEY even if extraction failed; update the block that assigns and
exports these variables to validate that both VAPID_PUBLIC_KEY and
VAPID_PRIVATE_KEY are non-empty after extraction (or check the grep/cut exit
status), and if either is empty print an error message including which key is
missing and exit with a non-zero status to fail fast instead of continuing.

In `@install/dependencies/os_version.sh`:
- Around line 12-15: The current check using lsb_release in the if block (the
lsb_release -d | grep -Eq "Ubuntu|Debian" check) will falsely fail on minimal
images where lsb_release is not installed; update the validation to first detect
whether the lsb_release command exists and only use it if present, otherwise
parse /etc/os-release (or /etc/debian_version as a fallback) to look for
"Ubuntu" or "Debian"; change the if condition around the lsb_release usage to
try lsb_release when available and fall back to checking /etc/os-release, and
keep the same error message/exit behavior if neither check indicates
Debian/Ubuntu.

In `@install/docker-compose.yml`:
- Around line 4-5: The image value uses a shell-style default substitution with
quoted default which becomes literal in Docker Compose; change the image key's
value from using ${DECIDIM_IMAGE:-"decidim/decidim:latest"} to an unquoted
default ${DECIDIM_IMAGE:-decidim/decidim:latest} so the DECIDIM_IMAGE
substitution resolves to decidim/decidim:latest when unset (match the worker
service's approach), ensuring the image name has no embedded quotes.

In `@install/install.sh`:
- Around line 67-75: After downloading "$TMP/deploy.zip" from "$REPOSITORY_URL"
but before unzipping or sourcing anything, add integrity/authenticity
verification: fetch the release checksum (e.g. deploy.zip.sha256) or signature
(e.g. deploy.zip.asc) from the same releases endpoint, verify the archive using
a strong hash (SHA256) or GPG public-key verification, and abort with a clear
error if verification fails; ensure this logic references the same
"$TMP/deploy.zip" artifact and exits the installer on mismatch so no untrusted
content is executed.

In `@install/scripts/entrypoint.sh`:
- Around line 1-22: The script lacks fail-fast shell options, so add strict
error handling (e.g., set -euo pipefail) immediately after the shebang in
entrypoint.sh so any failing command (like bundle install or migrations) causes
the container to exit; also avoid swallowing errors from critical commands by
removing stderr redirection on usermod/groupmod (or explicitly check their exit
status) so failures in usermod/groupmod or bundle install propagate and stop the
script.

In `@install/scripts/sidekiq_entrypoint.sh`:
- Line 1: The shebang currently enables shell tracing (-x) unconditionally in
sidekiq_entrypoint.sh; remove -x from the shebang and make tracing opt‑in by
checking an environment flag (e.g., SH_TRACE or TRACE_SHELL) at the top of the
script and running set -x only when that flag is enabled, ensuring tracing is
off by default to avoid leaking sensitive args.
- Around line 4-6: The script currently runs `bundle install` when `bundle
check` fails but does not stop if the install fails, causing confusing errors
later; update the sidekiq_entrypoint.sh logic so after running `bundle install`
you check its exit status and if it failed log an error and exit non-zero (so
the container/process fails fast) before reaching the final `exec` that starts
Sidekiq.

In `@README.md`:
- Line 142: Fix the typo and punctuation in the README sentence that currently
reads "To see the full list of Decidim Environment Variables, and that you can
add to your generated `.env` file, you can take a look at the oficial
[documentation](https://docs.decidim.org/en/develop/configure/environment_variables)";
change "oficial" to "official", remove the awkward comma after "Variables" and
rephrase for clarity (e.g., "To see the full list of Decidim environment
variables that you can add to your generated `.env` file, see the official
[documentation](https://docs.decidim.org/en/develop/configure/environment_variables)").
Make the same correction for the other identical occurrence elsewhere in the
document.
- Line 107: Change the phrase "production ready" to the hyphenated form
"production-ready" in the README text (the sentence starting "We've been working
on a script that you can use to have a fully functional, production ready
decidim instance.") so the compound adjective is correctly hyphenated as
"production-ready".
- Around line 109-111: The fenced code block containing the curl command is
missing a language tag; update the block delimiter from ``` to ```bash so the
snippet is marked as a Bash code block (locate the triple-backtick fence around
the curl example in README.md and replace it with ```bash).
- Line 119: Fix the README typo: replace the incorrect environment variable name
REPOSITOIRY_PATH with the correct REPOSITORY_PATH in the sentence mentioning the
default host directory (/opt/decidim). Update any other occurrences in README.md
to match the codebase's REPOSITORY_PATH used in install/install.sh,
install/up.sh, and install/dependencies/build_env.sh so documentation and
scripts are consistent.
🧹 Nitpick comments (3)
install/dependencies/open_ports.sh (1)

3-23: Avoid SSH lockout when enabling UFW.

The script always allows port 22 before enabling UFW. If SSH is configured on a different port, this can lock the user out. Consider prompting for the SSH port (default 22) or detecting it from sshd config.

💡 Suggested adjustment
 open_ports() {
   echo
   echo "To handle the SSL certificate we will have to open the port 80 and the port 443"
   echo
+
+  read -r -p "SSH port to keep open [22]: " SSH_PORT </dev/tty
+  SSH_PORT=${SSH_PORT:-22}

   if ! command -v ufw; then
     echo "UFW nos intalled. We are going to install it to allow openning ports 80 and 443 on this machine."
     sudo apt install ufw
   fi

-  sudo ufw allow 22
+  sudo ufw allow "$SSH_PORT"
   sudo ufw allow 80
   sudo ufw allow 443
   sudo ufw --force enable
 }
install/up.sh (1)

6-20: Consider making REPOSITORY_PATH and compose file path explicit for robustness.

While up.sh works correctly when sourced from install.sh (where REPOSITORY_PATH is set and the current directory is already the repository root), the script could be more resilient if run independently or from different contexts. Adding a safe default for REPOSITORY_PATH and explicitly passing the compose file path with -f would improve robustness without changing behavior in the intended use case.

🔧 Suggested improvements
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPOSITORY_PATH="${REPOSITORY_PATH:-"$(cd "$SCRIPT_DIR/.." && pwd)"}"
 ENV_FILE="${REPOSITORY_PATH}/.env"
+COMPOSE_FILE="${REPOSITORY_PATH}/docker-compose.yml"

 ...

-docker compose --env-file "$ENV_FILE" up -d
+docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" up -d

 ...

-docker compose logs --tail=30
+docker compose -f "$COMPOSE_FILE" logs --tail=30
install/dependencies/build_env.sh (1)

105-106: Mask secret inputs in interactive prompts.

Passwords/secret keys are currently echoed to the terminal. Use silent reads to reduce accidental exposure.

🔐 Suggested prompt hardening
-    read -r -p "Database password: " DATABASE_PASSWORD </dev/tty
+    read -r -s -p "Database password: " DATABASE_PASSWORD </dev/tty
+    echo
@@
-read -r -p "SMTP_PASSWORD: " SMTP_PASSWORD </dev/tty
+read -r -s -p "SMTP_PASSWORD: " SMTP_PASSWORD </dev/tty
+echo
@@
-  read -r -p "Secret Access Key: " AWS_SECRET_ACCESS_KEY </dev/tty
+  read -r -s -p "Secret Access Key: " AWS_SECRET_ACCESS_KEY </dev/tty
+  echo

Also applies to: 135-136, 158-158

Comment on lines +3 to +7
on:
push:
branches:
- main
workflow_dispatch:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/build_install_folder.yml

Repository: decidim/docker

Length of output: 172


🌐 Web query:

softprops/action-gh-release requirements tag ref behavior

💡 Result:

Summary of softprops/action-gh-release tag/ref behavior and requirements

  • tag_name input: defaults to github.ref (the workflow ref). Use the tag name you want with with.tag_name. Documentation shows tag_name defaults to github.ref. [1]
  • Expectation: the action is intended to run for tag pushes (refs/tags/*). README recommends gating the step or event to tag pushes (e.g., if: startsWith(github.ref, 'refs/tags/')). [1][2]
  • Practical requirement: the tag you reference must exist in the repository (a push of a tag or an explicit tag object). If you pass a tag_name that does not exist (or run the action on a branch ref), the release API call can fail (examples/issues show 403/failed release when using tag_name like "latest" without an actual tag). [2]
  • If you need to create a release for a branch commit, create a tag first (or ensure tag_name points to an existing tag/sha) before invoking the action. Alternatively, gate the action to tag pushes so github.ref will be a tag ref. [1][2]

Sources:
[1] softprops/action-gh-release README (usage, tag_name default and tag-gating).
[2] softprops/action-gh-release GitHub issue reporting failures when using non-existing tag_name (example "latest").


🏁 Script executed:

git ls-files | grep -E "\.github/workflows"

Repository: decidim/docker

Length of output: 132


Release step will fail on branch pushes without proper tag handling.

softprops/action-gh-release defaults tag_name to github.ref and requires an actual tag ref. Since this workflow triggers on push: branches: [main], the release step at lines 21-24 will fail with a failed release API call when invoked on branch pushes. Gate the release step with if: startsWith(github.ref, 'refs/tags/') or restrict the trigger to tag pushes only (e.g., on: push: tags: ['v*']).

🔧 Proposed fix (tag-only release)
-on:
-  push:
-    branches:
-      - main
+on:
+  push:
+    tags:
+      - 'v*'
   workflow_dispatch:

Or add a conditional gate to the release step:

       - name: Release
+        if: startsWith(github.ref, 'refs/tags/')
         uses: softprops/action-gh-release@v2
🤖 Prompt for AI Agents
In @.github/workflows/build_install_folder.yml around lines 3 - 7, The release
step using softprops/action-gh-release is vulnerable because tag_name defaults
to github.ref and the workflow is triggered on branch pushes; either restrict
the workflow trigger to tag pushes (e.g., change on: push to push: tags: ['v*'])
or add a conditional on the release job/step (use if: startsWith(github.ref,
'refs/tags/')) so the softprops/action-gh-release step (the step invoking
softprops/action-gh-release and setting tag_name) only runs for tag refs.

davidbeig and others added 10 commits January 22, 2026 10:40
Co-authored-by: Tom Greenwood <101816158+greenwoodt@users.noreply.github.com>
Co-authored-by: Tom Greenwood <101816158+greenwoodt@users.noreply.github.com>
Co-authored-by: Tom Greenwood <101816158+greenwoodt@users.noreply.github.com>
Co-authored-by: Tom Greenwood <101816158+greenwoodt@users.noreply.github.com>
Co-authored-by: Tom Greenwood <101816158+greenwoodt@users.noreply.github.com>
Co-authored-by: Tom Greenwood <101816158+greenwoodt@users.noreply.github.com>
Co-authored-by: Tom Greenwood <101816158+greenwoodt@users.noreply.github.com>
Co-authored-by: Tom Greenwood <101816158+greenwoodt@users.noreply.github.com>
Co-authored-by: Tom Greenwood <101816158+greenwoodt@users.noreply.github.com>
Co-authored-by: Tom Greenwood <101816158+greenwoodt@users.noreply.github.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@install/dependencies/build_env.sh`:
- Around line 168-175: The script branches to call get_storage_keys when the
user chooses S3 but never sets the STORAGE variable, causing failures under set
-u; update the code so STORAGE is set to a valid value when S3 is chosen (e.g.,
set STORAGE="s3")—either inside get_storage_keys or immediately after calling
get_storage_keys in the case for [Yy]*—and ensure any required export or default
fallback remains for other branches; reference the STORAGE variable and the
get_storage_keys call to locate where to add the assignment.
- Around line 263-271: The heredoc writing to BUILD_ENV_PATH currently includes
leading spaces before each AWS_* line (inside the STORAGE conditional), which
will be emitted literally into the .env; update the heredoc in build_env.sh so
the variable lines (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_BUCKET,
AWS_REGION, AWS_ENDPOINT) have no leading whitespace (or switch to an
unindented/left-aligned EOF marker) so the resulting file contains lines like
AWS_ACCESS_KEY_ID="..." with no leading spaces.
- Around line 6-11: The BUILD_ENV_PATH assignment uses an unsafe expansion of
REPOSITORY_PATH which under set -u will cause a crash before the subsequent
validation runs; fix by validating REPOSITORY_PATH exists before using it (move
the if [ -z "${REPOSITORY_PATH:-}" ] check above the BUILD_ENV_PATH assignment)
or change the assignment to use safe expansion (e.g.,
BUILD_ENV_PATH="${REPOSITORY_PATH:-}/.env") so the script does not error out
when REPOSITORY_PATH is unset.

In `@install/scripts/entrypoint.sh`:
- Around line 27-34: The script prints "✅ Migrations are all up" unconditionally
even when SKIP_MIGRATIONS is set; update the logic so the success message is
only echoed after running bundle exec rails db:migrate (when SKIP_MIGRATIONS is
unset), and emit a clear different message when SKIP_MIGRATIONS is set (or
remove the unconditional echo). Locate the SKIP_MIGRATIONS conditional and move
or conditionally guard the echo "✅ Migrations are all up" so it only runs after
the bundle exec rails db:migrate branch, leaving the existing echo "⚠️ Skipping
migrations!" in the else branch.
♻️ Duplicate comments (4)
install/scripts/entrypoint.sh (1)

1-14: Add fail-fast behavior for robustness.

The script lacks set -e, so failures in bundle install, rake, or rails db:migrate won't halt execution, potentially starting the container in a broken state. This was flagged in a previous review.

install/dependencies/generate_vapid_keys.sh (1)

21-26: Add validation for extracted VAPID keys.

If grep returns no matches (e.g., the Rails task output format changes or fails silently), empty keys are exported and the installer proceeds with invalid configuration. This will cause push notification failures at runtime.

Proposed fix
 VAPID_PUBLIC_KEY=$(echo "$output" | grep 'VAPID_PUBLIC_KEY' | cut -d'=' -f2)
 VAPID_PRIVATE_KEY=$(echo "$output" | grep 'VAPID_PRIVATE_KEY' | cut -d'=' -f2)
 
+if [ -z "$VAPID_PUBLIC_KEY" ] || [ -z "$VAPID_PRIVATE_KEY" ]; then
+  echo "❌ Failed to extract VAPID keys from generator output"
+  exit 1
+fi
+
 # Export the keys for use by calling script
 export VAPID_PUBLIC_KEY
 export VAPID_PRIVATE_KEY
install/dependencies/build_env.sh (2)

114-125: Initialize COMPOSE_PROFILES before the case statement to prevent crash.

COMPOSE_PROFILES is only set in build_local_database (line 68). When the user chooses external database, line 259 expands an unset variable under set -u, crashing the script.

Proposed fix
+COMPOSE_PROFILES=""
+
 case $yn in
 [Yy]*)
   EXTERNAL_DATABASE=true
   build_external_database
   ;;
 [Nn]*)
   EXTERNAL_DATABASE=false
   build_local_database
   ;;
 esac

191-192: Fix typo in variable name: MAPS_API_PROIVDERMAPS_API_PROVIDER.

The misspelled variable reference means any pre-existing MAPS_API_PROVIDER value is ignored, always defaulting to "here".

Proposed fix
 read -r -p "HERE API KEY: " MAPS_API_KEY </dev/tty
-MAPS_API_PROVIDER=${MAPS_API_PROIVDER=-here}
+MAPS_API_PROVIDER=${MAPS_API_PROVIDER:-here}
🧹 Nitpick comments (1)
install/dependencies/build_env.sh (1)

104-109: Consider hiding password input with read -rs.

Database password is visible as the user types. The same applies to SMTP_PASSWORD (line 136) and AWS_SECRET_ACCESS_KEY (line 158). Using -s suppresses echo for sensitive fields.

Example fix for this block
   while [ -z "${DATABASE_PASSWORD:-}" ]; do
-    read -r -p "Database password: " DATABASE_PASSWORD </dev/tty
+    read -rs -p "Database password: " DATABASE_PASSWORD </dev/tty
+    echo  # newline after hidden input
     if [ -z "$DATABASE_PASSWORD" ]; then
       echo "❌ Database password cannot be empty"
     fi
   done

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@install/install.sh`:
- Around line 39-42: The script currently sets REPOSITORY_PATH using only
DECIDIM_PATH, ignoring a user-set REPOSITORY_PATH; update the assignment for the
REPOSITORY_PATH variable so it first uses the environment-provided
REPOSITORY_PATH if present, then falls back to DECIDIM_PATH, and finally to the
default "/opt/decidim". Modify the REPOSITORY_PATH assignment (the
REPOSITORY_PATH variable near the top of the script) to evaluate in that
priority order and keep REPOSITORY_URL and REPOSITORY_BRANCH unchanged.

In `@install/up.sh`:
- Around line 6-12: The script assumes REPOSITORY_PATH is set and fails under
set -u; before defining ENV_FILE add a safe default for REPOSITORY_PATH (e.g.,
use shell parameter expansion to fall back to a sensible path such as the
repository root or the script's parent directory) so
ENV_FILE="${REPOSITORY_PATH}/.env" works when up.sh is run standalone; update
install/up.sh to set REPOSITORY_PATH if unset (affecting the ENV_FILE assignment
and subsequent checks) using the parameter expansion pattern so no runtime error
occurs when REPOSITORY_PATH is missing.
♻️ Duplicate comments (3)
install/dependencies/build_env.sh (1)

194-195: Fix map provider default assignment (-here typo).
Line 195 sets MAPS_API_PROVIDER to -here when unset, which is likely invalid and inconsistent with Line 252’s default. Use the standard :-here default.

🛠️ Proposed fix
-MAPS_API_PROVIDER=${MAPS_API_PROVIDER=-here}
+MAPS_API_PROVIDER=${MAPS_API_PROVIDER:-here}

If you want to confirm how the variable is consumed across scripts and compose files:

#!/bin/bash
rg -n "MAPS_(API_)?PROVIDER" -g '*.sh' -g '*.yml' -g '*.yaml'
install/install.sh (1)

67-75: Add integrity verification for deploy.zip before unzip.
Lines 67‑75 fetch and extract a remote archive that is later sourced. Without checksum/signature verification, a compromised release becomes RCE.

🔒 Suggested checksum verification
 curl -fsSL \
   --retry 3 \
   --retry-delay 2 \
   --connect-timeout 30 \
   --max-time 300 \
   --progress-bar \
   -o "$TMP/deploy.zip" \
   "$REPOSITORY_URL/releases/download/latest/deploy.zip"
+
+curl -fsSL \
+  -o "$TMP/deploy.zip.sha256" \
+  "$REPOSITORY_URL/releases/download/latest/deploy.zip.sha256"
+(cd "$TMP" && sha256sum -c deploy.zip.sha256)

If you want to confirm whether the release workflow already publishes checksums:

#!/bin/bash
rg -n "sha256|checksum" .github/workflows
README.md (1)

169-169: Add period after “etc.”
Line 169 should use “etc.).” for correct punctuation.

✏️ Suggested edit
-| **MAPS_PROVIDER** | `here` | app | Selects map provider (here, mapbox, google, etc). |
+| **MAPS_PROVIDER** | `here` | app | Selects map provider (here, mapbox, google, etc.). |
🧹 Nitpick comments (1)
install/dependencies/build_env.sh (1)

137-141: Hide secret inputs in prompts.
Lines 138 and 160 echo secrets to the terminal. Using read -s prevents shoulder‑surfing and keeps sensitive values out of scrollback.

🔒 Proposed update
-read -r -p "SMTP_PASSWORD: " SMTP_PASSWORD </dev/tty
+read -r -s -p "SMTP_PASSWORD: " SMTP_PASSWORD </dev/tty
+echo
@@
-read -r -p "Secret Access Key: " AWS_SECRET_ACCESS_KEY </dev/tty
+read -r -s -p "Secret Access Key: " AWS_SECRET_ACCESS_KEY </dev/tty
+echo

Please verify the prompt flow still renders clearly in your target terminals after masking input.

Also applies to: 159-161

Comment on lines +39 to +42
REPOSITORY_PATH=${DECIDIM_PATH:-/opt/decidim}
REPOSITORY_URL="https://github.com/decidim/docker"
REPOSITORY_BRANCH="feat/decidim_install"

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, check the full context of install/install.sh around lines 39-42
head -n 60 install/install.sh | tail -n +1

Repository: decidim/docker

Length of output: 2855


🏁 Script executed:

#!/bin/bash
# Search for usage of REPOSITORY_PATH and DECIDIM_PATH across the repo
rg -n "DECIDIM_PATH|REPOSITORY_PATH" -g '*.sh' -g '*.md' -B 2 -A 2

Repository: decidim/docker

Length of output: 2819


Respect the documented REPOSITORY_PATH environment variable override.

The script ignores any user-provided REPOSITORY_PATH and only reads DECIDIM_PATH, contradicting the documented behavior in README.md which states users can change the installation directory with the REPOSITORY_PATH environment variable.

Proposed fix
-REPOSITORY_PATH=${DECIDIM_PATH:-/opt/decidim}
+REPOSITORY_PATH=${REPOSITORY_PATH:-${DECIDIM_PATH:-/opt/decidim}}

This ensures the priority is: user-provided REPOSITORY_PATH > DECIDIM_PATH fallback > /opt/decidim default.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
REPOSITORY_PATH=${DECIDIM_PATH:-/opt/decidim}
REPOSITORY_URL="https://github.com/decidim/docker"
REPOSITORY_BRANCH="feat/decidim_install"
REPOSITORY_PATH=${REPOSITORY_PATH:-${DECIDIM_PATH:-/opt/decidim}}
REPOSITORY_URL="https://github.com/decidim/docker"
REPOSITORY_BRANCH="feat/decidim_install"
🤖 Prompt for AI Agents
In `@install/install.sh` around lines 39 - 42, The script currently sets
REPOSITORY_PATH using only DECIDIM_PATH, ignoring a user-set REPOSITORY_PATH;
update the assignment for the REPOSITORY_PATH variable so it first uses the
environment-provided REPOSITORY_PATH if present, then falls back to
DECIDIM_PATH, and finally to the default "/opt/decidim". Modify the
REPOSITORY_PATH assignment (the REPOSITORY_PATH variable near the top of the
script) to evaluate in that priority order and keep REPOSITORY_URL and
REPOSITORY_BRANCH unchanged.

Comment on lines +6 to +12
ENV_FILE="${REPOSITORY_PATH}/.env"

# Check if .env file exists
if [ ! -f "$ENV_FILE" ]; then
echo "❌ Error: .env file not found at $ENV_FILE"
echo "Please run the installation script first or create the .env file manually."
exit 1
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Read the full install/up.sh file
cat -n install/up.sh

Repository: decidim/docker

Length of output: 172


🏁 Script executed:

# Search for references to up.sh to understand invocation patterns
rg -n "up\.sh" --type sh --type md -A 2 -B 2

Repository: decidim/docker

Length of output: 276


🏁 Script executed:

# Check if there's a parent installation script that might set REPOSITORY_PATH
fd -e sh -path "*/install/*" | head -20

Repository: decidim/docker

Length of output: 226


🏁 Script executed:

# Read the full install/up.sh file
wc -l install/up.sh

Repository: decidim/docker

Length of output: 74


🏁 Script executed:

# Read install/up.sh
cat -n install/up.sh

Repository: decidim/docker

Length of output: 769


🏁 Script executed:

# Read install/install.sh around the source line to see REPOSITORY_PATH setup
sed -n '1,130p' install/install.sh | tail -50

Repository: decidim/docker

Length of output: 1349


🏁 Script executed:

# Check where REPOSITORY_PATH is set in install/install.sh
sed -n '1,130p' install/install.sh | head -100

Repository: decidim/docker

Length of output: 3854


🏁 Script executed:

# Get more context from the beginning of install/install.sh
head -50 install/install.sh

Repository: decidim/docker

Length of output: 2539


🏁 Script executed:

# Check if REPOSITORY_PATH is exported in install/install.sh
grep -n "export REPOSITORY_PATH" install/install.sh

Repository: decidim/docker

Length of output: 40


🏁 Script executed:

# Get the full context around REPOSITORY_PATH and its export
sed -n '40,50p' install/install.sh

Repository: decidim/docker

Length of output: 396


Add default for REPOSITORY_PATH to support standalone execution of up.sh.

While up.sh is normally sourced from install/install.sh (where REPOSITORY_PATH is set), the script would fail under set -u if invoked directly. Adding a default improves robustness:

Suggested fix
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPOSITORY_PATH="${REPOSITORY_PATH:-$SCRIPT_DIR}"
 ENV_FILE="${REPOSITORY_PATH}/.env"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ENV_FILE="${REPOSITORY_PATH}/.env"
# Check if .env file exists
if [ ! -f "$ENV_FILE" ]; then
echo "❌ Error: .env file not found at $ENV_FILE"
echo "Please run the installation script first or create the .env file manually."
exit 1
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPOSITORY_PATH="${REPOSITORY_PATH:-$SCRIPT_DIR}"
ENV_FILE="${REPOSITORY_PATH}/.env"
# Check if .env file exists
if [ ! -f "$ENV_FILE" ]; then
echo "❌ Error: .env file not found at $ENV_FILE"
echo "Please run the installation script first or create the .env file manually."
exit 1
🤖 Prompt for AI Agents
In `@install/up.sh` around lines 6 - 12, The script assumes REPOSITORY_PATH is set
and fails under set -u; before defining ENV_FILE add a safe default for
REPOSITORY_PATH (e.g., use shell parameter expansion to fall back to a sensible
path such as the repository root or the script's parent directory) so
ENV_FILE="${REPOSITORY_PATH}/.env" works when up.sh is run standalone; update
install/up.sh to set REPOSITORY_PATH if unset (affecting the ENV_FILE assignment
and subsequent checks) using the parameter expansion pattern so no runtime error
occurs when REPOSITORY_PATH is missing.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🤖 Fix all issues with AI agents
In `@install/docker-compose.yml`:
- Line 87: The Traefik dashboard is exposed without authentication via the
command flag --entrypoints.traefik.address=:8080; either disable the dashboard
by removing/enforcing --api=false in the Traefik service command or add
authentication middleware and attach it to the traefik router: create a basic
auth middleware (htpasswd) and reference it from the router that serves the
dashboard (the router named/labelled "traefik"), or add the auth via a
static/dynamic provider referenced by the service; update the docker-compose
Traefik service command/labels and the router configuration to ensure the
dashboard endpoint is protected.
- Around line 63-69: The worker service's environment omits a value for
BUNDLE_GEMFILE while the app service sets BUNDLE_GEMFILE=Gemfile.wrapper,
causing inconsistent Gemfile usage; update the worker service environment to
explicitly set BUNDLE_GEMFILE=Gemfile.wrapper so both services use the same
Gemfile (look for the worker environment block and the app service
BUNDLE_GEMFILE setting to ensure they match).
- Around line 19-25: The traefik label traefik.http.routers.app.rule currently
uses Host(`$DECIDIM_DOMAIN`) which Docker Compose will interpolate at compose
time and produce malformed labels; update the label to escape the dollar sign so
the runtime container receives the literal variable (use $$DECIDIM_DOMAIN) so
Traefik can resolve it at runtime. Locate the labels block containing
traefik.http.routers.app.rule and replace the single-dollar variable with a
double-dollar escape to prevent compose interpolation.

In `@install/README.md`:
- Line 249: The README has duplicate and incorrect section numbers: the heading
"6.3 Email Domain Authentication" should be renumbered to follow the prior
subsection (e.g., change the second "6.3" to "6.4" or the appropriate next
number), and the "Step 7" subsection references that currently use 6.1, 6.2, 6.3
must be updated to 7.1, 7.2, 7.3 respectively; locate the headings and the "Step
7" block in install/README.md (search for the literal headings "6.3 Email Domain
Authentication" and "Step 7") and update the numeric prefixes so numbering is
sequential and consistent across the file.
- Around line 294-297: The restart command in the README targets a non-existent
Docker service name ("sidekiq"); update the command to use the actual service
name defined in docker-compose.yml (replace "sidekiq" with "worker"), or
alternatively rename the service in docker-compose.yml to "sidekiq" if that
naming is preferred—ensure the README's "Restart Sidekiq if needed" section and
the docker-compose service names (worker) match.
- Around line 327-332: The README's update steps reference running "git pull"
but the installer delivers a zip (no git repo); remove or replace the git pull
step in the sequence shown and instead document the correct update flow: show
running "docker compose pull" then "docker compose up -d" to refresh images, and
add a short note telling users that to update installer scripts or configuration
they must re-run the installer or download the latest release (link to the
releases page); update the block in install/README.md that contains the commands
and add the clarifying note suggested in the review.
- Around line 421-430: The log command example uses the container name "decidim"
but docker compose logs expects a service name; update the example line "docker
compose logs -f decidim" to use the service name "app" instead (leave the "db"
example as-is), and add a brief note or comment clarifying that docker compose
logs accepts service names while docker logs uses container names so readers use
"app" for compose commands.
- Around line 69-73: The displayed ssh-keygen command is truncated ("ssh-keygen
-t rsa -b 4096 -C \"") causing a syntax error; update the snippet so the command
includes a placeholder email and the closing quote (e.g., replace the truncated
string with ssh-keygen -t rsa -b 4096 -C "your_email@example.com") and ensure
the fenced code block is properly closed; target the README snippet containing
the ssh-keygen command.
- Around line 7-14: The table of contents links are broken because the actual
headings differ: the heading for server creation contains the parenthetical
"(Hetzner Example)" so its auto-generated anchor is
"#step-1-create-a-server-hetzner-example" (not "#step-1-create-a-server"), and
the final setup heading is numbered "Step 7: Complete Setup" while the TOC
points to "#step-6-complete-setup"; update the TOC entry 2 to use
"#step-1-create-a-server-hetzner-example" (or remove the parenthetical from the
heading) and update TOC entry 7 (the "Step 6/7" entry) so the link matches the
heading by either renaming the heading to "Step 6: Complete Setup" or changing
the TOC link to "#step-7-complete-setup".
🧹 Nitpick comments (5)
install/docker-compose.yml (4)

76-77: Replace deprecated links with depends_on.

The links directive is deprecated in modern Docker Compose. Service discovery via container names works automatically on the default network. Use depends_on if you need startup ordering.

♻️ Proposed fix
-    links:
-      - cache
+    depends_on:
+      - cache

84-84: Consider reducing Traefik log level for production.

DEBUG level logging is verbose and may expose sensitive request/response details. For a production deployment guide, INFO or WARN would be more appropriate defaults.

♻️ Proposed fix
-      - --log.level=DEBUG
+      - --log.level=INFO

46-52: Inconsistent pull_policy between app and worker services.

The worker service has pull_policy: always (line 51) but the app service doesn't. For consistency and to ensure both services use the same image version, consider adding the same policy to the app service.

♻️ Proposed fix for app service
   app:
     container_name: decidim
     image: ${DECIDIM_IMAGE:-decidim/decidim:latest}
+    pull_policy: always
     command: ["bin/rails", "server", "-b", "0.0.0.0"]

116-121: Consider adding health checks for Redis cache service.

The PR objectives mention adding container health checks as a planned improvement. Adding a health check to Redis would allow dependent services to wait for Redis to be ready.

♻️ Example health check
   cache:
     image: redis:8-alpine
     container_name: decidim_cache
     volumes:
       - cache_data:/data
     restart: always
+    healthcheck:
+      test: ["CMD", "redis-cli", "ping"]
+      interval: 10s
+      timeout: 5s
+      retries: 3
install/README.md (1)

182-188: Use proper headings instead of bold emphasis for option categories.

Lines 182 and 185 use bold text (**Free Options**, **Paid Options**) as pseudo-headings. This affects document structure and accessibility. Consider using proper heading levels.

📝 Proposed fix
-**Free Options**
+#### Free Options
 - [Gmail SMTP](https://support.google.com/a/answer/176600) (500 emails/day limit)

-**Paid Options**
+#### Paid Options
 - [Scaleway](https://www.scaleway.com/en/transactional-email-tem/)

Comment on lines +19 to +25
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`$DECIDIM_DOMAIN`)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls=true"
- "traefik.http.routers.app.tls.certresolver=myresolver"
- "traefik.http.services.app.loadbalancer.server.port=3000"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find all references to DECIDIM_DOMAIN in the codebase
rg "DECIDIM_DOMAIN" -t yml -t yaml -t env -t sh

Repository: decidim/docker

Length of output: 85


🏁 Script executed:

# Look for .env template files
fd -t f "\.(env|example)" install/ || fd -t f "\.env" . | head -20

Repository: decidim/docker

Length of output: 40


🏁 Script executed:

# Examine the full docker-compose.yml to understand context
cat -n install/docker-compose.yml | head -40

Repository: decidim/docker

Length of output: 1712


🏁 Script executed:

# Search for DECIDIM_DOMAIN in all file types
rg "DECIDIM_DOMAIN" --type-list | head -5

Repository: decidim/docker

Length of output: 447


🏁 Script executed:

# Try broader search for DECIDIM_DOMAIN
rg "DECIDIM_DOMAIN"

Repository: decidim/docker

Length of output: 898


🏁 Script executed:

# Check if there's a .env.example or similar in the install directory
ls -la install/ | grep -E "env|example"

Repository: decidim/docker

Length of output: 40


🏁 Script executed:

# Look at more of the docker-compose.yml for context
cat -n install/docker-compose.yml

Repository: decidim/docker

Length of output: 5071


🏁 Script executed:

# Check if there's a .env file or .env.example in the repository
fd -t f "\.env" --max-one-result install/

Repository: decidim/docker

Length of output: 291


🏁 Script executed:

# Look at how other labels use variables in the same file
grep -n "traefik\." install/docker-compose.yml | head -10

Repository: decidim/docker

Length of output: 544


🏁 Script executed:

# Check how DECIDIM_DOMAIN is handled in the build script
cat -n install/dependencies/build_env.sh | grep -A 2 -B 2 "DECIDIM_DOMAIN"

Repository: decidim/docker

Length of output: 1061


🏁 Script executed:

# Check if there's a .env file or .env.example
find install -name ".env*" -o -name "env.example"

Repository: decidim/docker

Length of output: 40


🏁 Script executed:

# Look at the actual syntax patterns used in docker-compose.yml more broadly
grep -E "\$\{|\$[A-Z_]+" install/docker-compose.yml | head -15

Repository: decidim/docker

Length of output: 40


🏁 Script executed:

# Check the actual docker-compose interpolation behavior by looking for documentation or comments
grep -r "interpolat\|expand\|\${\|\$[A-Z]" install/ --include="*.md" --include="*.sh" | head -10

Repository: decidim/docker

Length of output: 790


🏁 Script executed:

# Check how the .env is actually created and what values it contains
cat install/dependencies/build_env.sh | tail -50

Repository: decidim/docker

Length of output: 1393


Use $$DECIDIM_DOMAIN to prevent Docker Compose from interpolating the variable.

Docker Compose will interpolate $DECIDIM_DOMAIN using the value from .env, which is written with quotes (DECIDIM_DOMAIN="example.com"). This causes the label to become malformed: Host(\"example.com"`). Use $$` to escape the dollar sign so Docker Compose passes it literally to the container, allowing Traefik to resolve the variable from the runtime environment.

🤖 Prompt for AI Agents
In `@install/docker-compose.yml` around lines 19 - 25, The traefik label
traefik.http.routers.app.rule currently uses Host(`$DECIDIM_DOMAIN`) which
Docker Compose will interpolate at compose time and produce malformed labels;
update the label to escape the dollar sign so the runtime container receives the
literal variable (use $$DECIDIM_DOMAIN) so Traefik can resolve it at runtime.
Locate the labels block containing traefik.http.routers.app.rule and replace the
single-dollar variable with a double-dollar escape to prevent compose
interpolation.

Comment on lines +63 to +69
environment:
- BUNDLE_GEMFILE
- DATABASE_URL
- SECRET_KEY_BASE
- DECIDIM_FORCE_SSL=false
- QUEUE_ADAPTER=sidekiq
- REDIS_URL=${REDIS_URL:-redis://decidim_cache:6379/0}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Worker service has inconsistent BUNDLE_GEMFILE configuration.

Line 64 declares BUNDLE_GEMFILE without a value, meaning it will be empty unless set in .env. However, the app service explicitly sets BUNDLE_GEMFILE=Gemfile.wrapper (line 29). This inconsistency may cause the worker to use a different Gemfile than the app, leading to gem version mismatches or missing dependencies.

🛠️ Proposed fix
     environment:
-      - BUNDLE_GEMFILE
+      - BUNDLE_GEMFILE=Gemfile.wrapper
       - DATABASE_URL
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
environment:
- BUNDLE_GEMFILE
- DATABASE_URL
- SECRET_KEY_BASE
- DECIDIM_FORCE_SSL=false
- QUEUE_ADAPTER=sidekiq
- REDIS_URL=${REDIS_URL:-redis://decidim_cache:6379/0}
environment:
- BUNDLE_GEMFILE=Gemfile.wrapper
- DATABASE_URL
- SECRET_KEY_BASE
- DECIDIM_FORCE_SSL=false
- QUEUE_ADAPTER=sidekiq
- REDIS_URL=${REDIS_URL:-redis://decidim_cache:6379/0}
🤖 Prompt for AI Agents
In `@install/docker-compose.yml` around lines 63 - 69, The worker service's
environment omits a value for BUNDLE_GEMFILE while the app service sets
BUNDLE_GEMFILE=Gemfile.wrapper, causing inconsistent Gemfile usage; update the
worker service environment to explicitly set BUNDLE_GEMFILE=Gemfile.wrapper so
both services use the same Gemfile (look for the worker environment block and
the app service BUNDLE_GEMFILE setting to ensure they match).

- --log.level=DEBUG
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --entrypoints.traefik.address=:8080
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Traefik dashboard is exposed without authentication.

The Traefik API/dashboard is enabled on port 8080 and routed via the traefik router, but no authentication middleware is configured. While port 8080 isn't published to the host, the dashboard is still accessible if someone accesses the container directly or if the routing exposes it. Consider either disabling the dashboard (--api=false) or adding basic auth middleware.

🔒 Option 1: Disable the dashboard
     command:
-      - --api
+      - --api.dashboard=false
🔒 Option 2: Add authentication (requires generating htpasswd)
     labels:
       - "traefik.enable=true"
       - "traefik.http.routers.traefik.service=api@internal"
+      - "traefik.http.routers.traefik.middlewares=auth"
+      - "traefik.http.middlewares.auth.basicauth.users=${TRAEFIK_DASHBOARD_AUTH}"

Also applies to: 101-103

🤖 Prompt for AI Agents
In `@install/docker-compose.yml` at line 87, The Traefik dashboard is exposed
without authentication via the command flag --entrypoints.traefik.address=:8080;
either disable the dashboard by removing/enforcing --api=false in the Traefik
service command or add authentication middleware and attach it to the traefik
router: create a basic auth middleware (htpasswd) and reference it from the
router that serves the dashboard (the router named/labelled "traefik"), or add
the auth via a static/dynamic provider referenced by the service; update the
docker-compose Traefik service command/labels and the router configuration to
ensure the dashboard endpoint is protected.

Comment on lines +7 to +14
1. [Prerequisites](#prerequisites)
2. [Step 1: Create a Server](#step-1-create-a-server)
3. [Step 2: Connect to Your Server](#step-2-connect-to-your-server)
4. [Step 3: Configure DNS](#step-3-configure-dns)
5. [Step 4: Install Decidim](#step-4-install-decidim)
6. [Step 5: Configure Email (SMTP)](#step-5-configure-email-smtp)
7. [Step 6: Complete Setup](#step-6-complete-setup)
8. [Troubleshooting](#troubleshooting)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n install/README.md | head -30

Repository: decidim/docker

Length of output: 1309


🏁 Script executed:

grep -n "^#" install/README.md

Repository: decidim/docker

Length of output: 1927


Fix invalid table of contents anchor links due to heading mismatches.

Two issues prevent the TOC links from working:

  1. Line 39 heading includes "(Hetzner Example)" — the auto-generated anchor will be #step-1-create-a-server-hetzner-example, not #step-1-create-a-server
  2. Line 257 heading is "Step 7: Complete Setup" but TOC entry references #step-6-complete-setup — the document numbering is inconsistent (shows Step 7, not Step 6)

Update the TOC anchor for item 2 to include the extra text, or simplify the heading. For item 7, either renumber the heading to "Step 6" or update the TOC link to #step-7-complete-setup.

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

7-7: Link fragments should be valid

(MD051, link-fragments)


8-8: Link fragments should be valid

(MD051, link-fragments)


9-9: Link fragments should be valid

(MD051, link-fragments)


10-10: Link fragments should be valid

(MD051, link-fragments)


11-11: Link fragments should be valid

(MD051, link-fragments)


12-12: Link fragments should be valid

(MD051, link-fragments)


13-13: Link fragments should be valid

(MD051, link-fragments)


14-14: Link fragments should be valid

(MD051, link-fragments)

🤖 Prompt for AI Agents
In `@install/README.md` around lines 7 - 14, The table of contents links are
broken because the actual headings differ: the heading for server creation
contains the parenthetical "(Hetzner Example)" so its auto-generated anchor is
"#step-1-create-a-server-hetzner-example" (not "#step-1-create-a-server"), and
the final setup heading is numbered "Step 7: Complete Setup" while the TOC
points to "#step-6-complete-setup"; update the TOC entry 2 to use
"#step-1-create-a-server-hetzner-example" (or remove the parenthetical from the
heading) and update TOC entry 7 (the "Step 6/7" entry) so the link matches the
heading by either renaming the heading to "Step 6: Complete Setup" or changing
the TOC link to "#step-7-complete-setup".

Comment on lines +69 to +73
```bash
ssh-keygen -t rsa -b 4096 -C "
```
This will generate a public/private key pair. Copy the contents of `~/.ssh/id_rsa.pub` and paste it into Hetzner's SSH key field.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Incomplete ssh-keygen command will confuse users.

The command is truncated and missing the email address placeholder and closing quote. This will cause a syntax error or leave users stuck.

📝 Proposed fix
-  ```bash
-  ssh-keygen -t rsa -b 4096 -C "
-  ```
+  ```bash
+  ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
+  ```
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```bash
ssh-keygen -t rsa -b 4096 -C "
```
This will generate a public/private key pair. Copy the contents of `~/.ssh/id_rsa.pub` and paste it into Hetzner's SSH key field.
🤖 Prompt for AI Agents
In `@install/README.md` around lines 69 - 73, The displayed ssh-keygen command is
truncated ("ssh-keygen -t rsa -b 4096 -C \"") causing a syntax error; update the
snippet so the command includes a placeholder email and the closing quote (e.g.,
replace the truncated string with ssh-keygen -t rsa -b 4096 -C
"your_email@example.com") and ensure the fenced code block is properly closed;
target the README snippet containing the ssh-keygen command.

4. **Monitor logs**: Check for suspicious activity
5. **Backup regularly**: Database and configuration files
### 6.3 Email Domain Authentication
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Section numbering is inconsistent.

There are two sections numbered "6.3" (lines 239 and 249), and "Step 7" (line 257) uses subsection numbers 6.1, 6.2, 6.3 instead of 7.1, 7.2, 7.3.

📝 Proposed fix
-### 6.3 Email Domain Authentication
+### 6.4 Email Domain Authentication
...
 ## ✅ Step 7: Complete Setup

-### 6.1 Create System Administrator
+### 7.1 Create System Administrator
...
-### 6.2 Access Your Decidim Instance
+### 7.2 Access Your Decidim Instance
...
-### 6.3 Configure Your Organization
+### 7.3 Configure Your Organization
...
-### 6.4 Background Jobs & Maintenance
+### 7.4 Background Jobs & Maintenance

Also applies to: 257-259

🤖 Prompt for AI Agents
In `@install/README.md` at line 249, The README has duplicate and incorrect
section numbers: the heading "6.3 Email Domain Authentication" should be
renumbered to follow the prior subsection (e.g., change the second "6.3" to
"6.4" or the appropriate next number), and the "Step 7" subsection references
that currently use 6.1, 6.2, 6.3 must be updated to 7.1, 7.2, 7.3 respectively;
locate the headings and the "Step 7" block in install/README.md (search for the
literal headings "6.3 Email Domain Authentication" and "Step 7") and update the
numeric prefixes so numbering is sequential and consistent across the file.

Comment on lines +294 to +297
# Restart Sidekiq if needed
docker compose restart sidekiq
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Incorrect service name in restart command.

The command references sidekiq but the service is named worker in docker-compose.yml.

📝 Proposed fix
 # Restart Sidekiq if needed
-docker compose restart sidekiq
+docker compose restart worker
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Restart Sidekiq if needed
docker compose restart sidekiq
```
# Restart Sidekiq if needed
docker compose restart worker
🤖 Prompt for AI Agents
In `@install/README.md` around lines 294 - 297, The restart command in the README
targets a non-existent Docker service name ("sidekiq"); update the command to
use the actual service name defined in docker-compose.yml (replace "sidekiq"
with "worker"), or alternatively rename the service in docker-compose.yml to
"sidekiq" if that naming is preferred—ensure the README's "Restart Sidekiq if
needed" section and the docker-compose service names (worker) match.

Comment on lines +327 to +332
```bash
cd /opt/decidim
git pull
docker compose pull
docker compose up -d
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Update instructions reference git pull but installation uses zip download.

The installation is done via curl | bash which downloads a zip file, not a git clone. Users won't have a git repository to pull from. Consider providing alternative update instructions or clarifying the intended update workflow.

📝 Suggested alternative
 ### Updating Decidim

 ```bash
 cd /opt/decidim
-git pull
-docker compose pull
-docker compose up -d
+# Pull the latest Docker images
+docker compose pull
+# Restart with updated images
+docker compose up -d

+> Note: For script and configuration updates, re-run the installer or check the releases page for the latest install package.

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

In @install/README.md around lines 327 - 332, The README's update steps
reference running "git pull" but the installer delivers a zip (no git repo);
remove or replace the git pull step in the sequence shown and instead document
the correct update flow: show running "docker compose pull" then "docker compose
up -d" to refresh images, and add a short note telling users that to update
installer scripts or configuration they must re-run the installer or download
the latest release (link to the releases page); update the block in
install/README.md that contains the commands and add the clarifying note
suggested in the review.


</details>

<!-- fingerprinting:phantom:poseidon:ocelot -->

<!-- This is an auto-generated comment by CodeRabbit -->

Comment on lines +421 to +430
```bash
# Application logs
docker compose logs -f decidim
# Database logs
docker compose logs -f db
# All services
docker compose logs -f
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Service name mismatch in log command example.

The command uses decidim which is the container name, but docker compose logs expects the service name app. While container names work with docker logs, they don't work with docker compose logs.

📝 Proposed fix
 # Application logs
-docker compose logs -f decidim
+docker compose logs -f app

 # Database logs
 docker compose logs -f db
🤖 Prompt for AI Agents
In `@install/README.md` around lines 421 - 430, The log command example uses the
container name "decidim" but docker compose logs expects a service name; update
the example line "docker compose logs -f decidim" to use the service name "app"
instead (leave the "db" example as-is), and add a brief note or comment
clarifying that docker compose logs accepts service names while docker logs uses
container names so readers use "app" for compose commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create a decidim-install.sh script that can handle the installation of a Decidim instance

5 participants