Files
computer-playbook/roles/web-app-gitea/templates/compose.yml.j2
kevinveenbirkenbachandClaude Opus 5.5 9413aeba08 fix(web-app-gitea): give the MCP sidecar a healthcheck that can run
gitea-mcp-server 1.8.0 is the first release that ships a HEALTHCHECK,
and it is written in shell form for an image that has no shell. Every
probe ends in 'exec: "/bin/sh": no such file or directory', the
container turns unhealthy after three attempts, and Swarm replaces the
task about every 72 seconds, so the service never converges.

The role now declares the probe itself, in exec form and with the port
the sidecar listens on: without it the probe asks 8080 whatever the
server uses. The command took that port from the adapter's entry, it
now reads the sidecar's own.

The adapter waits for a healthy sidecar, so a probe that cannot pass
fails a compose deploy as well and not only a Swarm one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 02:10:25 +02:00

72 lines
3.1 KiB
Django/Jinja

{% include 'roles/sys-svc-compose/templates/base.yml.j2' %}
{% set service_name = 'gitea' %}
{{ service_name }}:
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
{{ lookup('container_image', application_id, 'gitea') }}
{{ lookup('compose_only', 'container_name', GITEA_CONTAINER) }}
{{ lookup('container_ports',
['gitea', 'http', DOCKER_BIND_HOST],
['gitea', 'ssh']) | indent(4) }}
{{ lookup('container_volumes', service_name) | indent(4) }}
{{ lookup('container_healthcheck', service_name) | indent(4) }}
{{ lookup('container_extra_hosts') | indent(4) }}
{{ lookup('container_networks') | indent(4) }}
{% if GITEA_MCP_ENABLED | bool %}
{{ GITEA_MCP_UPSTREAM_NETWORK }}:
{% endif %}
{{ lookup('container_depends_on', application_id) | indent(4) }}
{% if GITEA_MCP_ENABLED | bool %}
{% set service_name = GITEA_MCP_SIDECAR_SERVICE_KEY %}
{{ service_name }}:
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
{{ lookup('container_image', application_id, 'giteamcpupstream') }}
{{ lookup('compose_only', 'container_name', GITEA_MCP_SIDECAR_CONTAINER) }}
command: {{ GITEA_MCP_COMMAND | to_json }}
environment:
GITEA_HOST: "{{ GITEA_MCP_UPSTREAM_URL }}"
GITEA_SCOPES: "{{ GITEA_MCP_TOOL_SCOPES }}"
GITEA_READONLY: "true"
{{ lookup('container_healthcheck', service_name) | indent(4) }}
networks: # nocheck: networks-literal the sidecar authenticates nobody, so it joins the private upstream net and nothing else
{{ GITEA_MCP_UPSTREAM_NETWORK }}:
{{ lookup('depends_on', {'gitea': 'service_healthy'}) }}
{% set service_name = GITEA_MCP_SERVICE_KEY %}
{% set docker_compose_env = [GITEA_MCP_INSTANCE_DIR, 'upstream.env'] | path_join %}
{{ service_name }}:
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
{{ lookup('container_image', application_id, 'giteamcp') }}
build:
context: {{ GITEA_MCP_INSTANCE_DIR }}
dockerfile: Dockerfile
args:
MCP_ADAPTER_BASE_IMAGE: {{ lookup('config', application_id, 'services.giteamcp.image') }}
MCP_ADAPTER_BASE_VERSION: "{{ lookup('config', application_id, 'services.giteamcp.version') }}"
{{ lookup('compose_only', 'container_name', GITEA_MCP_CONTAINER) }}
read_only: true
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
environment:
ADAPTER_CONTRACT: {{ GITEA_MCP_CONTRACT | to_json | to_json | replace('$', '$$') }}
ADAPTER_BEARER: {{ GITEA_MCP_BEARER | to_json }}
ADAPTER_PORT: "{{ GITEA_MCP_PORT }}"
expose:
- "{{ GITEA_MCP_PORT }}"
{{ lookup('depends_on', {GITEA_MCP_SIDECAR_SERVICE_KEY: 'service_healthy'}) }}
{{ lookup('container_healthcheck', 'giteamcp') | indent(4) }}
{{ lookup('container_networks', own_network_only=True, provider_self_alias=False) | indent(4) }}
{{ GITEA_MCP_UPSTREAM_NETWORK }}:
{% endif %}
{{ lookup('compose_volumes') }}
{{ lookup('compose_networks') }}
{% if GITEA_MCP_ENABLED | bool %}
{{ GITEA_MCP_UPSTREAM_NETWORK }}:
driver: {{ 'overlay' if DEPLOYMENT_MODE == 'swarm' else 'bridge' }}
internal: true
{% endif %}