-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcompose.coolify.yml
More file actions
166 lines (161 loc) · 7.58 KB
/
Copy pathcompose.coolify.yml
File metadata and controls
166 lines (161 loc) · 7.58 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
# A board on your own machine, deployed by Coolify.
#
# The same four services as `compose.yml`, with four differences. Three
# are Coolify's doing:
#
# - **No published ports.** Coolify's proxy routes to the container and issues
# the certificate. A `ports:` mapping here would put the board on the host's
# port as well, bypassing the proxy and the TLS with it.
# - **The secrets are generated.** `SERVICE_BASE64_64_*` and
# `SERVICE_PASSWORD_*` are Coolify's magic variables: it fills them in on the
# first deploy, keeps them for the life of the resource, and shows them in
# the UI. Nothing here needs a value typed into it, which is the entire
# reason this file exists beside the ordinary compose file.
# - **The board is told its own URL.** `SERVICE_FQDN_WEB_3000` asks Coolify for
# a domain pointed at port 3000, and `SERVICE_URL_WEB` is that domain with a
# scheme, which is what `APP_URL` wants.
#
# And one is the release pipeline's: the image is pulled from the registry,
# not built here, and the pin is an **exact version** that only a release
# commit moves. A restart or a Redeploy re-creates precisely what is pinned —
# no deploy path resolves "the newest anything", so the board never changes
# version without a commit on the branch saying so. See docs/release.md.
#
# MEITH_IMAGE exists for CI, which runs this file against the image it just
# built — and for the operator who wants to pin by digest or hold a version:
# set it on the resource's environment in Coolify and it wins over the file.
#
# Requires Coolify v4.0.0-beta.411 or newer — magic variables in a compose file
# from a Git source arrived in that release. See docs/quickstart.md.
services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: community
# Generated by Coolify, once, and reused by every service below.
#
# `SERVICE_PASSWORD_` rather than one of the base64 variants, and that is
# not cosmetic: this value is substituted into the postgres:// URL three
# services below, and base64's alphabet includes `/` and `+`, which make
# the connection string unparseable. Coolify's plain password generator
# emits alphanumerics. The two `SERVICE_BASE64_64_` values below are never
# part of a URL, so there the wider alphabet is free entropy.
POSTGRES_PASSWORD: $SERVICE_PASSWORD_POSTGRES
POSTGRES_DB: community
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U community -d community']
interval: 10s
timeout: 5s
retries: 5
# Runs to completion, then exits. `web` waits for it, so the schema is always
# applied before the first request rather than racing it — and an upgrade that
# fails to migrate never serves the new code at all.
migrate:
image: ${MEITH_IMAGE:-ghcr.io/meith-dev/meith:0.11.0}
environment:
COMMUNITY_ROLE: migrate
DATABASE_URL: postgres://community:$SERVICE_PASSWORD_POSTGRES@postgres:5432/community
DATA_SOURCE: postgres
# The migration signs nothing, and still needs these.
#
# `env` is one environment contract for every process the board runs, with
# no per-role exemptions — so the production rules apply to the migrator
# too, and it exits naming both secrets before it opens a connection. That
# is the trade the single schema buys: no role can quietly boot with less
# than the others. Passing them here costs one line; the alternative is a
# carve-out that would let the *web* server start without them one day.
AUTH_SECRET: $SERVICE_BASE64_64_AUTH
TICK_SECRET: $SERVICE_BASE64_64_TICK
depends_on:
postgres:
condition: service_healthy
restart: 'no'
# No healthcheck here: the image carries a role-aware one (see
# docker/healthcheck.sh), which asks the web server for `/api/health` and the
# worker whether its process is alive. A single web-shaped check declared per
# service is how every worker container ended up permanently unhealthy.
web:
image: ${MEITH_IMAGE:-ghcr.io/meith-dev/meith:0.11.0}
restart: unless-stopped
environment:
# Ask Coolify for a domain on port 3000, then hand the board the same
# thing with a scheme in front. Without APP_URL, every link in an e-mail
# has nothing to be absolute against.
- SERVICE_FQDN_WEB_3000
- APP_URL=$SERVICE_URL_WEB
- DATABASE_URL=postgres://community:$SERVICE_PASSWORD_POSTGRES@postgres:5432/community
- DATA_SOURCE=postgres
# 64 characters of randomness each, generated on the first deploy. These
# are the two values a board cannot have a default for: a shipped
# AUTH_SECRET is an unsubscribe link anybody who has read the source can
# forge, and a shipped TICK_SECRET is a tick anybody can drive.
- AUTH_SECRET=$SERVICE_BASE64_64_AUTH
- TICK_SECRET=$SERVICE_BASE64_64_TICK
- QUEUE_DRIVER=postgres
- CACHE_DRIVER=next
# A real disk, shared with the worker below, which is what makes `local`
# the right answer here rather than the trap it is on a serverless host.
- FILESTORE_DRIVER=local
# Left unset, mail is configured on the board itself — the installer asks
# on first run and /admin/settings?group=mail changes it afterwards, with
# no redeploy. Set MAIL_DRIVER here and this file wins instead.
# See docs/operating.md § Mail.
- MAIL_DRIVER=${MAIL_DRIVER:-log}
- MAIL_HTTP_ENDPOINT=${MAIL_HTTP_ENDPOINT:-}
- MAIL_HTTP_TOKEN=${MAIL_HTTP_TOKEN:-}
- MAIL_SMTP_HOST=${MAIL_SMTP_HOST:-}
- MAIL_SMTP_PORT=${MAIL_SMTP_PORT:-}
- MAIL_SMTP_SECURITY=${MAIL_SMTP_SECURITY:-}
- MAIL_SMTP_USERNAME=${MAIL_SMTP_USERNAME:-}
- MAIL_SMTP_PASSWORD=${MAIL_SMTP_PASSWORD:-}
- MAIL_FROM=${MAIL_FROM:-}
volumes:
- uploads:/app/.uploads
depends_on:
postgres:
condition: service_healthy
migrate:
condition: service_completed_successfully
# The tick, as a process rather than a schedule. Every catch-up operation runs
# here — queued mail leaves, bans expire, digests send — so a board without
# this service is one where nothing fails and nothing happens.
worker:
image: ${MEITH_IMAGE:-ghcr.io/meith-dev/meith:0.11.0}
restart: unless-stopped
environment:
- COMMUNITY_ROLE=worker
- DATABASE_URL=postgres://community:$SERVICE_PASSWORD_POSTGRES@postgres:5432/community
- DATA_SOURCE=postgres
- AUTH_SECRET=$SERVICE_BASE64_64_AUTH
- TICK_SECRET=$SERVICE_BASE64_64_TICK
- APP_URL=$SERVICE_URL_WEB
- QUEUE_DRIVER=postgres
# Not `next`: the worker is a plain Node process, so a cache backed by a
# framework that is not running would be a cache of nothing.
- CACHE_DRIVER=memory
- FILESTORE_DRIVER=local
- MAIL_DRIVER=${MAIL_DRIVER:-log}
- MAIL_HTTP_ENDPOINT=${MAIL_HTTP_ENDPOINT:-}
- MAIL_HTTP_TOKEN=${MAIL_HTTP_TOKEN:-}
- MAIL_SMTP_HOST=${MAIL_SMTP_HOST:-}
- MAIL_SMTP_PORT=${MAIL_SMTP_PORT:-}
- MAIL_SMTP_SECURITY=${MAIL_SMTP_SECURITY:-}
- MAIL_SMTP_USERNAME=${MAIL_SMTP_USERNAME:-}
- MAIL_SMTP_PASSWORD=${MAIL_SMTP_PASSWORD:-}
- MAIL_FROM=${MAIL_FROM:-}
volumes:
# The same volume the web service mounts. Both halves of the board write
# uploads, and a worker with its own copy would re-encode an avatar the
# web server cannot serve.
- uploads:/app/.uploads
depends_on:
postgres:
condition: service_healthy
migrate:
condition: service_completed_successfully
volumes:
pgdata:
uploads: