Files
ipcr-forge/Apps/Radicle/docker-compose.yml
T
2026-10-03 16:09:47 +02:00

335 lines
16 KiB
YAML

name: radicle
services:
# The web UI: the Radicle Explorer bundle served as static files by Caddy.
# It is a pure client-side app — every request it makes goes from the visitor's
# browser straight to the API below, which is why that one needs its own domain.
radicle:
image: dirk1980/radicle-explorer-container@sha256:0e31485c38a765e83a10b1a4bf71b12d4f9fec630881d171bba736a4a56a16ce
container_name: radicle
user: $PUID:$PGID
restart: unless-stopped
cpu_shares: 80
deploy:
resources:
limits:
memory: 128M
environment:
TZ: $TZ
# Caddy writes its autosaved config and its (unused here) certificate store
# under these two paths. The image points them at /config and /data, which are
# root-owned, so as $PUID the process cannot create them and refuses to start.
XDG_CONFIG_HOME: /tmp/caddy-config
XDG_DATA_HOME: /tmp/caddy-data
volumes:
# Replaces the image's own :80 Caddyfile — see the comments in that file.
- ${DATA_ROOT:-/DATA}/AppData/$AppID/caddy/Caddyfile:/etc/caddy/Caddyfile:ro
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:8080/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
expose:
- 8080
labels:
caddy_0: radicle-${APP_DOMAIN}
caddy_0.import: gateway_tls
caddy_0.reverse_proxy: "{{upstreams 8080}}"
caddy_1: radicle-${APP_PUBLIC_IP_DASH}.nip.io
caddy_1.import: gateway_tls
caddy_1.reverse_proxy: "{{upstreams 8080}}"
caddy_2: radicle-${APP_PUBLIC_IP_DASH}.sslip.io
caddy_2.reverse_proxy: "{{upstreams 8080}}"
networks:
- pcs
# radicle-httpd — the node's read-only JSON API, on its own domain.
#
# This domain is deliberately NOT behind the SSO gate: the explorer above is a
# static bundle running in the visitor's browser, so it is the browser that calls
# this API, cross-origin. The daemon serves public repositories only (private ones
# are filtered out upstream) and has no write endpoints at all. See rationale.md.
radicle-api:
image: dirk1980/radicle-httpd@sha256:5e95be30e4bde55b509a65fd56f8b825cafd0f5cd752c1a13e79aad75b958895
container_name: radicle-api
user: $PUID:$PGID
restart: unless-stopped
cpu_shares: 70
deploy:
resources:
limits:
memory: 512M
command: ["--listen", "0.0.0.0:8080"]
environment:
TZ: $TZ
volumes:
# Same Radicle home as the node: the daemon reads the repository storage
# directly and talks to the node over the control socket inside it.
- ${DATA_ROOT:-/DATA}/AppData/$AppID/home/:/radicle-home/
healthcheck:
# curl, not wget: this image carries curl and no busybox.
test: ["CMD", "curl", "-fsS", "-o", "/dev/null", "http://127.0.0.1:8080/api/v1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
expose:
- 8080
labels:
caddy_0: radicle-api-${APP_DOMAIN}
caddy_0.import: gateway_tls
caddy_0.reverse_proxy: "{{upstreams 8080}}"
caddy_1: radicle-api-${APP_PUBLIC_IP_DASH}.nip.io
caddy_1.import: gateway_tls
caddy_1.reverse_proxy: "{{upstreams 8080}}"
caddy_2: radicle-api-${APP_PUBLIC_IP_DASH}.sslip.io
caddy_2.reverse_proxy: "{{upstreams 8080}}"
networks:
- pcs
# The node itself: speaks the Radicle peer-to-peer protocol (gossip, fetch,
# announce) on TCP 8776. No HTTP, so no Caddy route and no place on the shared
# network — it only needs egress, which the app-internal bridge provides.
radicle-node:
image: dirk1980/radicle-seed-node@sha256:78ccee526ad4b4f4d7c79c2323b032a7209858a48e1dc9c077272ff1660050e2
container_name: radicle-node
user: $PUID:$PGID
restart: unless-stopped
cpu_shares: 50
deploy:
resources:
limits:
memory: 1G
environment:
TZ: $TZ
volumes:
- ${DATA_ROOT:-/DATA}/AppData/$AppID/home/:/radicle-home/
ports:
# Radicle peer-to-peer protocol. Other nodes dial in here to fetch the
# repositories this node seeds; without it the node still syncs outbound,
# but nobody can fetch from it. Hence the needs-public-ip tag.
- "8776:8776"
healthcheck:
# `rad node status` round-trips the node's control socket, which is the only
# liveness signal this container has — but it also renders the peer table, and
# that takes 3-8s on an idle node and longer while the node is syncing. Hence
# the unusually generous timeout: a 10s one fails during the first sync and
# flaps the container between healthy and unhealthy.
test: ["CMD", "rad", "node", "status"]
interval: 60s
timeout: 30s
retries: 3
start_period: 90s
networks:
- radicle-internal
networks:
pcs:
name: ${APP_NET:-pcs}
external: true
# Egress-only network for the node. Nothing else joins it — the node and the
# API daemon share state through the Radicle home directory, not over TCP.
radicle-internal:
driver: bridge
x-casaos:
architectures:
- amd64
main: radicle
developer: Radicle
category: Developer
tags: ["developer", "personal", "needs-public-ip"]
description:
en_us: |
Radicle — your own peer-to-peer forge
Radicle is a sovereign alternative to GitHub and GitLab. Instead of a company's server holding the canonical copy of your code, every participant keeps a full replica and gossips changes directly to peers. Repositories, issues and patches are all Git objects, signed by their author and replicated across the network — there is no central service to go down, change its terms, or lock you out.
This app runs a complete Radicle node on your server, plus the official Radicle Explorer web interface to browse it:
• Your node stays online and keeps your repositories available even when your laptop is not
• Anything you push from your machine is replicated here and served to the network
• Issues, patches and code are browsable in your own web UI, on your own domain
• Anyone can clone your repositories over plain HTTPS, no account required
The web interface is for browsing. Creating repositories, opening issues and proposing patches happen with the `rad` command line on your own machine — the node here is what keeps them alive and reachable.
Repositories are only replicated once you seed them. A fresh node starts empty and stores nothing until you tell it what to follow.
fr_fr: |
Radicle — votre forge pair-à-pair
Radicle est une alternative souveraine à GitHub et GitLab. Plutôt qu'un serveur d'entreprise détenant la copie de référence de votre code, chaque participant conserve un réplica complet et propage ses modifications directement à ses pairs. Dépôts, tickets et patches sont tous des objets Git, signés par leur auteur et répliqués sur le réseau — aucun service central ne peut tomber, changer ses conditions ou vous verrouiller dehors.
Cette application fait tourner un nœud Radicle complet sur votre serveur, ainsi que l'interface web officielle Radicle Explorer pour le parcourir :
• Votre nœud reste en ligne et garde vos dépôts disponibles même quand votre portable ne l'est pas
• Tout ce que vous poussez depuis votre machine est répliqué ici et servi au réseau
• Tickets, patches et code se consultent dans votre propre interface web, sur votre domaine
• N'importe qui peut cloner vos dépôts en HTTPS, sans compte
L'interface web sert à consulter. Créer des dépôts, ouvrir des tickets et proposer des patches se fait avec la ligne de commande `rad` sur votre machine — le nœud installé ici est ce qui les maintient vivants et accessibles.
Les dépôts ne sont répliqués qu'une fois que vous les suivez. Un nœud neuf démarre vide et ne stocke rien tant que vous ne lui avez rien indiqué.
zh_cn: |
Radicle — 属于你自己的点对点代码托管
Radicle 是 GitHub 与 GitLab 的自主替代方案。代码的权威副本不再由某家公司的服务器保管,而是每个参与者都持有完整副本,并直接向对等节点传播更改。仓库、议题和补丁都是 Git 对象,由作者签名并在网络中复制——没有中心服务会宕机、修改条款或将你拒之门外。
此应用在你的服务器上运行一个完整的 Radicle 节点,并附带官方的 Radicle Explorer 网页界面:
• 节点持续在线,即使你的笔记本关机,仓库依然可用
• 你从本机推送的内容会复制到这里,并向网络提供服务
• 议题、补丁和代码都可以在你自己的域名下浏览
• 任何人都可以通过 HTTPS 克隆你的仓库,无需账户
网页界面用于浏览。创建仓库、提交议题和提出补丁请在本机使用 `rad` 命令行完成——这里的节点负责让它们保持在线、可被获取。
仓库只有在你选择跟随(seed)之后才会被复制。全新节点启动时是空的,在你指定之前不会存储任何内容。
icon: icon.png
screenshot_link:
- screenshot-1.png
- screenshot-2.png
- screenshot-3.png
tagline:
en_us: Your own peer-to-peer Git forge — a Radicle node and web explorer on your domain.
fr_fr: Votre forge Git pair-à-pair — un nœud Radicle et son explorateur web sur votre domaine.
zh_cn: 你自己的点对点 Git 托管——运行在你域名下的 Radicle 节点与网页浏览器。
thumbnail: thumbnail.png
tips:
before_install:
en_us: |
This app is public on purpose
A Radicle node is a participant in a public peer-to-peer network. Both of
its addresses are served directly to the public, bypassing the server's
single sign-on:
| Address | What it serves |
| ------- | -------------- |
| `https://radicle-$APP_DOMAIN` | The web interface |
| `https://radicle-api-$APP_DOMAIN/api/v1` | The read-only API the interface calls |
Only **public** repositories are served, the API has no write endpoints, and
a fresh node holds nothing until you seed something. See `rationale.md`.
Connecting your machine
Install the `rad` CLI from [radicle.dev](https://radicle.dev), then push a
repository and tell this node to seed it. Your node's identity (its Node ID)
is printed at `https://radicle-api-$APP_DOMAIN/api/v1` under `nid`.
Peer-to-peer port
The node publishes TCP **8776** so other peers can fetch from it. On a server
without a directly reachable public IP the app still works — your repositories
replicate outward to public seeds — but nobody fetches from you directly.
fr_fr: |
Cette application est publique, volontairement
Un nœud Radicle participe à un réseau pair-à-pair public. Ses deux adresses
sont servies directement au public, en contournant l'authentification
unique du serveur :
| Adresse | Ce qu'elle sert |
| ------- | --------------- |
| `https://radicle-$APP_DOMAIN` | L'interface web |
| `https://radicle-api-$APP_DOMAIN/api/v1` | L'API en lecture seule appelée par l'interface |
Seuls les dépôts **publics** sont servis, l'API n'a aucun point d'écriture, et
un nœud neuf ne contient rien tant que vous n'avez rien suivi. Voir
`rationale.md`.
Connecter votre machine
Installez la CLI `rad` depuis [radicle.dev](https://radicle.dev), puis poussez
un dépôt et demandez à ce nœud de le suivre. L'identité de votre nœud (Node ID)
est affichée sur `https://radicle-api-$APP_DOMAIN/api/v1`, champ `nid`.
Port pair-à-pair
Le nœud publie le port TCP **8776** pour que les autres pairs puissent récupérer
ses dépôts. Sur un serveur sans IP publique directement joignable, l'application
fonctionne quand même — vos dépôts se répliquent vers les seeds publics — mais
personne ne récupère directement chez vous.
zh_cn: |
此应用有意保持公开
Radicle 节点是公开点对点网络的参与者。它的两个地址都直接向公众开放,
绕过服务器的单点登录:
| 地址 | 提供的内容 |
| ---- | ---------- |
| `https://radicle-$APP_DOMAIN` | 网页界面 |
| `https://radicle-api-$APP_DOMAIN/api/v1` | 界面调用的只读 API |
仅提供**公开**仓库,API 没有任何写入接口,全新节点在你指定跟随之前不含任何内容。
详见 `rationale.md`。
连接你的机器
从 [radicle.dev](https://radicle.dev) 安装 `rad` 命令行,然后推送仓库并让此节点
跟随它。节点身份(Node ID)显示在 `https://radicle-api-$APP_DOMAIN/api/v1` 的
`nid` 字段。
点对点端口
节点公开 TCP **8776**,供其他对等节点抓取。若服务器没有可直接访问的公网 IP,应用
仍可使用——你的仓库会向公共 seed 节点复制——但别人无法直接从你这里抓取。
title:
en_us: Radicle
index: /
webui_port: 8080
store_app_id: radicle
x-compose-app:
schema_version: 2
folders:
- path: /DATA/AppData/$AppID/home
user: $PUID
group: $PGID
- path: /DATA/AppData/$AppID/caddy
user: $PUID
group: $PGID
init:
# Creates the node's Ed25519 identity. Radicle has no first-run wizard: without
# a key pair in the Radicle home, radicle-node exits immediately. `rad auth`
# prompts for an optional passphrase, hence the empty line on stdin — a seed
# node's key secures peer connections and signs nothing permanent, so upstream's
# own guidance is to leave it unencrypted (a passphrase would have to be typed
# in at every start).
- name: init-identity
image: dirk1980/radicle-seed-node@sha256:78ccee526ad4b4f4d7c79c2323b032a7209858a48e1dc9c077272ff1660050e2
user: $PUID:$PGID
volumes:
- /DATA/AppData/$AppID/home:/radicle-home
command: ["sh", "-c", "yes '' | rad auth --alias radicle-${APP_PUBLIC_IP_DASH} --stdin"]
when: absent:/DATA/AppData/$AppID/home/keys/radicle
files:
# The node's config carries the deployment's own addresses, so it is re-rendered
# on every up rather than frozen at install. `rad auth` above writes a default
# config.json first; this replaces it with ours and keeps it in step if the PCS
# domain or public IP ever changes.
#
# It also carries the bootstrap seeds, and those are NOT the compiled-in defaults.
# radicle 1.10 ships `iris.radicle.network` / `rosa.radicle.network`, whose IPv4
# addresses refuse connections on 8776 (verified from two networks, 2026-09-04) —
# a node configured with them never finds a peer. The same two nodes answer under
# their previous hostnames, which is what this file uses; radicle logs a rename
# warning for them and keeps working. Revisit when upstream's new addresses accept
# peer traffic again.
- path: /DATA/AppData/$AppID/home/config.json
from: home/config.json.tmpl
ensure: always
mode: "0644"
# Carries this deployment's API domain in a redirect, so it is re-rendered too.
- path: /DATA/AppData/$AppID/caddy/Caddyfile
from: caddy/Caddyfile.tmpl
ensure: always
mode: "0644"
# The landing path is handled by the Caddy redirect above, not here: neither
# `webui-path` nor `x-casaos.index` is interpolated, so a ${...} written in
# either reaches the browser as a literal.
webui-host: radicle-${domain}
webui-path: /