fix(decidim): migrate before serving so health means migrated

Decidim ran RAILS_ENV=development against a database that was created but
never migrated. Only development.rb carries
config.active_record.migration_error = :page_load, which inserts a
pre-routing guard that raises ActiveRecord::PendingMigrationError on every
request -- so the healthcheck's `curl -f /up` got a 151 kB debug page and
swarm reaped the tasks.

The role could never recover, because its ordering is a deadlock:
02_manager_ops.yml:26 `meta: flush_handlers` runs the converge gate, and
only afterwards does 01_setup.yml migrate -- behind its own wait for a
healthy container. Health needs migrations; migrations need health.

Clearnet runs hid this for months. DECIDIM_FORCE_SSL was true there, so
ActionDispatch::SSL answered every probe with a 301 before any app code
ran, and `curl -f` treats 301 as success -- it reported healthy even for
/health_check, a path this image does not route. Giving Decidim an .onion
primary domain turns TLS off (utils/tls_common.py resolve_enabled), the
301 disappears, and the pre-existing defect becomes visible.

Let the container migrate itself, so "healthy" means "migrated and
serving" in compose and swarm alike. An `until` loop rather than `&&`:
Rails takes a pg advisory lock and raises ConcurrentMigrationError on
contention, which would push two of three fresh replicas into a restart
loop. Measured: migrate takes ~37s on an empty database, both concurrent
replicas reach healthy at 133s/123s, the loser recovers after retrying.

The probe therefore needs an initialization window (start_period 180s,
interval 10s; the old 30s/1m/3 kills the task at ~211s), and the converge
gate needs the same 600-retry knob ten other slow roles already set.

Keep the Ansible migrate task as an idempotent safety net, and keep
`path: up` -- /health_check does not exist in this image and only ever
passed because of the 301.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5 committed 2026-08-10 09:13:06 +02:00
1 parent cc41c6b73d
commit 1518512fcf
3 files changed
+4 -1

No files matched your search

+2
View File
@@ -30,6 +30,8 @@ decidim:
healthcheck:
flavor: curl
path: up
interval: 10s
start_period: 180s
modes:
compose:
enabled: true
@@ -13,7 +13,7 @@
DECIDIM_PATCH_RUBY_DOCKER: "{{ DECIDIM_PATCH_RUBY_DOCKER }}"
{{ lookup('container_image', application_id, 'decidim', version=DECIDIM_VERSION ~ '-infinito') }}
{{ lookup('compose_only', 'container_name', DECIDIM_CONTAINER) }}
command: bundle exec puma -C config/puma.rb
command: bash -c "until bundle exec rails db:migrate; do sleep 5; done; exec bundle exec puma -C config/puma.rb"
{{ lookup('container_ports', ['decidim', 'http', DOCKER_BIND_HOST]) | indent(4) }}
{{ lookup('container_volumes', application_id, 'decidim') | indent(4) }}
{{ lookup('container_networks') | indent(4) }}
+1
View File
@@ -1,6 +1,7 @@
---
application_id: "web-app-decidim"
domain: "{{ lookup('domain', application_id) }}"
swarm_stack_converge_retries: 600
DECIDIM_VERSION: "{{ lookup('config', application_id, 'services.decidim.version') }}"
DECIDIM_IMAGE: "{{ lookup('config', application_id, 'services.decidim.image') }}"