Repository navigation
Expand file tree
/
Copy pathdocker-compose.local.yml
More file actions
200 lines (195 loc) · 7.12 KB
/
Copy pathdocker-compose.local.yml
File metadata and controls
200 lines (195 loc) · 7.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
services:
ci-hub-db:
container_name: ci-hub-db
image: postgres:14
pull_policy: if_not_present
restart: unless-stopped
stop_grace_period: 1m
volumes:
- ci_hub_pgdata:/var/lib/postgresql/data
command: -p 6543
ports:
- 6543:6543
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
POSTGRES_USER: companion
POSTGRES_DB: companiondb
healthcheck:
test: ["CMD-SHELL", "pg_isready -d companiondb -U companion -p 6543"]
interval: 5s
timeout: 10s
retries: 120
networks:
- ci_hub_network
- ci_os_hub_network
ci-hub-queue:
container_name: ci-hub-queue
image: rabbitmq:4-alpine
pull_policy: if_not_present
restart: unless-stopped
healthcheck:
test: ["CMD", "rabbitmqctl", "status"]
interval: 10s
timeout: 30s
retries: 10
start_period: 60s
ports:
- 5672:5672
environment:
RABBITMQ_DEFAULT_USER: companion
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD:-admin}
networks:
ci_hub_network:
aliases:
- ci-os-hub-queue
ci_os_hub_network:
aliases:
- ci-os-hub-queue
ci-hub:
# SECURITY: the Hub container does NOT run privileged — it manages app
# containers via the host Docker daemon over the mounted docker.sock, so it
# only needs socket *access* (user + group_add: DOCKER_GID), not host
# devices/capabilities. See the matching note in docker-compose.prod.yml.
# Add a scoped `cap_add:` if a future feature genuinely needs a capability.
# Keeps a concrete default, unlike the environment passthrough below: compose renders an
# empty `${VAR:-}` as the literal string, so this would become `user: ":"` and fail to
# parse. A `user:` directive also means the container never starts as root, so the
# entrypoint takes its already-unprivileged branch and cannot derive anything anyway.
user: "${CI_HUB_CONTAINER_UID:-1000}:${CI_HUB_CONTAINER_GID:-1000}"
group_add:
- "${DOCKER_GID:-973}"
build:
context: .
dockerfile: Dockerfile.dev
# Default to Docker's standard build network for cross-platform builds.
# Linux operators who need host-DNS behavior can opt in with
# DOCKER_BUILD_NETWORK=host.
network: ${DOCKER_BUILD_NETWORK:-default}
command: sh -lc "pnpm --filter @ci-hub/common run build && npx turbo run dev --filter backend --filter frontend --filter @ci-hub/common"
depends_on:
ci-hub-db:
condition: service_healthy
ci-hub-queue:
condition: service_healthy
container_name: ci-hub
restart: unless-stopped
healthcheck:
start_period: 30s
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health/live"]
interval: 10s
timeout: 5s
retries: 10
ports:
- 3000:3000
- 5005:5005
- 9091:9091
volumes:
# Hot reload
- ./packages/backend/src:/app/packages/backend/src
- ./packages/frontend/src:/app/packages/frontend/src
- ./packages/common/src:/app/packages/common/src
# Migrations
- ./packages/backend/src/core/database/drizzle:/app/assets/migrations
# Data
- .internal/media:/data/media
- .internal/state:/data/state
- .internal/repos:/data/repos
- ../CI-App-Store:/data/repos/migrated
- .internal/apps:/data/apps
- .internal/logs:/data/logs
- .internal/user-config:/data/user-config
- .internal/app-data:/app-data
- .internal/backups:/data/backups
# Static
- ${ENV_FILE:-.env}:/data/.env
- ${ROOT_FOLDER_HOST:-.internal}/cache:/data/cache
- ./docker-compose.local.yml:/data/docker-compose.yml
- ${DOCKER_SOCKET_PATH:-/var/run/docker.sock}:/var/run/docker.sock:ro
- ${ROOT_FOLDER_HOST:-.internal}/.docker:/data/.docker
- /proc/meminfo:/host/proc/meminfo:ro
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
- /etc/machine-id:/etc/machine-id:ro
networks:
ci_hub_network:
aliases:
- ci-os-hub
ci_os_hub_network:
aliases:
- ci-os-hub
environment:
HUB_CONTAINER_NAME: ci-hub
LOCAL: true
NODE_ENV: development
HOME: /data/user-config
CI_HUB_DATA_DIR: /data
ROOT_FOLDER_HOST: ${PWD}/.internal
DOCKER_CONFIG: /data/.docker
# Passed through EMPTY, not defaulted to 1000, so the entrypoint's own derivation is
# authoritative. `docker-entrypoint.sh` picks the owner of /data when this is unset —
# 1000 for a user-owned install, 0 for a root-owned one, where it then skips the
# privilege drop. Defaulting here made the variable ALWAYS set, so that derivation was
# dead code on the only path that matters, and a root-owned install crash-looped on
# EACCES reading its own 0600 /data/.env.
# Compose renders `${VAR:-}` as the empty string rather than omitting the key, and the
# image's BusyBox `sh` treats an empty value as unset for `${VAR:-default}` — both
# verified against the shipped image, because the whole fix rests on them.
CI_HUB_CONTAINER_UID: ${CI_HUB_CONTAINER_UID:-}
CI_HUB_CONTAINER_GID: ${CI_HUB_CONTAINER_GID:-}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
POSTGRES_USERNAME: companion
POSTGRES_DBNAME: companiondb
RABBITMQ_USERNAME: companion
RABBITMQ_PASSWORD: ${RABBITMQ_PASSWORD:-admin}
RABBITMQ_HOST: ci-hub-queue
INTERNAL_IP: 127.0.0.1
CI_HUB_VERSION: ${CI_HUB_VERSION:-0.2.27}
LOG_LEVEL: debug
OLLAMA_URL: http://host.docker.internal:11434
VLLM_URL: ${VLLM_URL:-http://host.docker.internal:8000}
# Empty unless the operator sets it: the Hub then probes oMLX's default
# (host.docker.internal:8000) only on Apple Silicon. A default here would
# skip that gate and probe vLLM's :8000 as oMLX on every Linux node.
OMLX_URL: ${OMLX_URL:-}
VLLM_API_KEY: ${VLLM_API_KEY:-}
OMLX_API_KEY: ${OMLX_API_KEY:-}
LEMONADE_URL: ${LEMONADE_URL:-http://host.docker.internal:13305}
extra_hosts:
- "host.docker.internal:host-gateway"
# See docker-compose.prod.yml: retry Docker embedded-DNS lookups instead of failing
# with EAI_AGAIN during network churn (#933).
dns_opt:
- attempts:5
- timeout:2
env_file:
- path: ${ENV_FILE:-.env}
required: false
labels:
# ---- General ----- #
ci-hub.managed: true
ci-os-hub.managed: true
# Cloudflare tunnel for dev — opt-in via: docker compose --profile cloudflare up cloudflared -d
cloudflared:
image: cloudflare/cloudflared:2026.10.5
pull_policy: if_not_present
container_name: cloudflared
restart: unless-stopped
command: tunnel run --token-file /home/nonroot/.cloudflared/token
volumes:
- ./tunnel:/home/nonroot/.cloudflared:ro
networks:
- ci_hub_network
- ci_os_hub_network
extra_hosts:
- "host.docker.internal:host-gateway"
profiles:
- cloudflare
networks:
ci_hub_network:
driver: bridge
name: ci-hub_network
ci_os_hub_network:
driver: bridge
name: ci-os-hub_network
volumes:
ci_hub_pgdata: