repos
/ orchard main

orchard

mirror

Every site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.

blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics

2.5 KB · 83 lines · YAML Raw History
 1name: orchard-edge
 2
 3# The shared edge: one tunnel and one Caddy in front of every site in this repo,
 4# plus the ntfy every alert is published to. cloudflared's state lives in a named
 5# volume seeded by setup-tunnel.sh rather than a bind mount off the host.
 6
 7services:
 8  caddy:
 9    build: ./caddy
10    container_name: orchard-caddy
11    restart: unless-stopped
12    networks: [edge]
13    security_opt:
14      - no-new-privileges:true
15    # Caddy access-logs every request for every hostname, so without this the
16    # container log is the one unbounded thing in the system.
17    logging:
18      driver: json-file
19      options:
20        max-size: "10m"
21        max-file: "3"
22
23  # The alert path. status and logging publish by container name on this bridge
24  # with a write-only token, and a phone reads over the tunnel with a read-only
25  # account. setup-ntfy.sh creates both, and the config is in ntfy/server.yml.
26  ntfy:
27    build: ./ntfy
28    container_name: orchard-ntfy
29    restart: unless-stopped
30    command: serve
31    volumes:
32      - ntfy:/var/lib/ntfy
33    # No published port, like every site here. Caddy allows the subscribe routes
34    # on the public hostname and refuses every publish route.
35    networks: [edge]
36    security_opt:
37      - no-new-privileges:true
38    healthcheck:
39      test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1:8000/v1/health"]
40      interval: 30s
41      timeout: 5s
42      retries: 3
43      start_period: 10s
44    logging:
45      driver: json-file
46      options:
47        max-size: "10m"
48        max-file: "3"
49
50  cloudflared:
51    image: cloudflare/cloudflared:2026.8.3@sha256:9be48e4b4e996da851bf78f7782bfab150dd4d8889e469d004802e7d7afb63b1
52    container_name: orchard-cloudflared
53    restart: unless-stopped
54    command: tunnel --config /etc/cloudflared/config.yml run
55    volumes:
56      - cloudflared:/etc/cloudflared:ro
57    networks: [edge]
58    depends_on: [caddy]
59    security_opt:
60      - no-new-privileges:true
61    logging:
62      driver: json-file
63      options:
64        max-size: "10m"
65        max-file: "3"
66
67networks:
68  edge:
69    name: orchard-edge
70
71volumes:
72  # Created and populated by setup-tunnel.sh before this stack first runs, so
73  # compose attaches to it rather than owning it.
74  cloudflared:
75    name: orchard-cloudflared
76    external: true
77
78  # Data rather than cache: cache.db holds alerts already delivered so an
79  # offline phone catches up, and user.db holds the two accounts. Lose this and
80  # setup-ntfy.sh has to run again and both publishers need a new token.
81  ntfy:
82    name: orchard-ntfy-data