Skip to content

Commit 334e489

Browse files
chore: improve docker-compose.dev (usememos#2938)
1 parent c782251 commit 334e489

File tree

1 file changed

+320
-55
lines changed

1 file changed

+320
-55
lines changed

scripts/docker-compose.dev.yaml

Lines changed: 320 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,339 @@
1+
# > Memos development environment <
2+
#
3+
# Available profiles: sqlite, mysql, postgres.
4+
# Use `docker compose --profile PROFILE_NAME up` to launch only services within the profile.
5+
#
6+
# Services in the `tools` profile are used for running one-off tasks like linting, generating code, etc.
7+
#
8+
# Services started in all database profiles:
9+
# Front-end: http://localhost:3001
10+
# API: http://localhost:8081
11+
# Adminer: http://localhost:8091
12+
#
13+
# On Windows, run this before using docker-compose on a new terminal:
14+
# $Env:HOME=$Env:USERPROFILE
15+
#
16+
# > Start Memos in development mode:
17+
# docker compose -f ./scripts/docker-compose.dev.yaml --profile [sqlite|mysql|postgres] up --detach
18+
#
19+
# > Stop all services:
20+
# docker compose -f ./scripts/docker-compose.dev.yaml --profile sqlite --profile postgres --profile mysql down
21+
#
22+
# > Remove related volumes: (all other files are mapped to ./air/docker/ directory)
23+
# docker volume rm memos-dev_pnpm-store memos-dev_node-modules
24+
#
25+
# One-off tasks:
26+
# > pnpm:
27+
# docker compose -f ./scripts/docker-compose.dev.yaml run --rm pnpm [add|remove|update] [PACKAGE_NAME] [--save-dev]
28+
#
29+
# > buf: (run this after modifying .proto files)
30+
# docker compose -f ./scripts/docker-compose.dev.yaml run --rm buf generate
31+
#
32+
# > go:
33+
# docker compose -f ./scripts/docker-compose.dev.yaml run --rm go mod tidy -go=1.22
34+
#
35+
# > golangci-lint: (run this before submitting Pull Requests affecting Go code)
36+
# docker compose -f ./scripts/docker-compose.dev.yaml run --rm golangci-lint run
37+
#
38+
# > goimports: (run this if golangci-lint shows "File is not `goimports`-ed"
39+
# docker compose -f ./scripts/docker-compose.dev.yaml run --rm goimports -local https://github.com/usememos/memos -w [FILE|.]
40+
#
141
version: "3.0"
242
name: memos-dev
43+
volumes:
44+
# pnpm uses hard links and node_modules uses symlinks.
45+
# Using volumes make things work properly on any host OS.
46+
node-modules:
47+
pnpm-store:
348
services:
4-
db:
5-
image: mysql
6-
volumes:
7-
- ./../.air/mysql:/var/lib/mysql
8-
environment:
9-
MYSQL_ALLOW_EMPTY_PASSWORD: yes
10-
MYSQL_DATABASE: memos
11-
api:
12-
image: cosmtrek/air
13-
working_dir: /work
14-
command: ["-c", "./scripts/.air.toml"]
15-
environment:
16-
- "MEMOS_DSN=root@tcp(db)/memos"
17-
- "MEMOS_DRIVER=mysql"
18-
volumes:
19-
- ./..:/work/
20-
- ./../.air/go-build:/root/.cache/go-build
21-
- $HOME/go/pkg/:/go/pkg/ # Cache for go mod shared with the host
2249
web:
50+
profiles: ["sqlite", "mysql", "postgres"]
2351
image: node:20-alpine
24-
working_dir: /work
25-
depends_on: ["api"]
26-
ports: ["3001:3001"]
27-
environment: ["DEV_PROXY_SERVER=http://api:8081/"]
52+
ports: [3001:3001]
53+
environment:
54+
DEV_PROXY_SERVER: http://api:8081/
55+
NPM_CONFIG_UPDATE_NOTIFIER: false
56+
working_dir: &web-working-dir /work/web
2857
entrypoint: ["/bin/sh", "-c"]
2958
command: ["corepack enable && pnpm i --frozen-lockfile && pnpm dev"]
30-
tmpfs: /work/node_modules/:exec # To avoid pnpm ERR_PNPM_LINKING_FAILED error
31-
volumes:
32-
- ./../web:/work
59+
tmpfs: &web-tmpfs /work/node_modules/:exec # avoid ERR_PNPM_LINKING_FAILED
60+
volumes: &web-volumes
61+
- node-modules:/work/web/node_modules
62+
- pnpm-store:/work/web/.pnpm-store
63+
- ../proto:/work/proto
64+
- ../web:/work/web
65+
healthcheck:
66+
test: ["CMD", "wget", "-qO", "-", "http://localhost:3001"]
67+
interval: 10s
68+
timeout: 5s
69+
70+
api:
71+
profiles: ["sqlite"]
72+
image: &api-image golang:1.22-alpine
73+
ports: &api-ports [8081:8081]
74+
environment:
75+
MEMOS_DRIVER: sqlite
76+
MEMOS_DATA: /var/opt/memos
77+
working_dir: &api-working-dir /work
78+
volumes: &api-volumes
79+
- $HOME/go/pkg/:/go/pkg/ # Share go mod cache with host
80+
- ../.air/docker/go-build:/root/.cache/go-build
81+
- ../.air/docker/go/bin:/go/bin
82+
- ../.air/docker/memosdata:/var/opt/memos
83+
- ..:/work/
84+
configs: &api-configs
85+
- source: air-entrypoint.sh
86+
target: /usr/local/bin/entrypoint.sh
87+
entrypoint: &api-entrypoint ["/bin/sh", "/usr/local/bin/entrypoint.sh"]
88+
command: &api-command ["-c", "./scripts/.air.toml"]
89+
healthcheck: &api-healthcheck
90+
test: ["CMD", "wget", "-qO", "-", "http://localhost:8081/api/v1/ping"]
91+
interval: 10s
92+
timeout: 5s
3393

34-
# Services below are used for developers to run once
35-
#
36-
# You can just run `docker compose run --rm SERVICE_NAME` to use
37-
# For example:
38-
# To regenerate typescript code of gRPC proto
39-
# Just run `docker compose run --rm buf`
40-
#
41-
# All of theses services belongs to profile 'tools'
42-
# This will prevent to launch by normally `docker compose up` unexpectly
94+
api-mysql:
95+
profiles: ["mysql"]
96+
depends_on: { mysql: { condition: service_healthy } }
97+
hostname: api
98+
environment:
99+
{ MEMOS_DRIVER: mysql, MEMOS_DSN: memos:memos@tcp(mysql)/memos }
100+
image: *api-image
101+
ports: *api-ports
102+
working_dir: *api-working-dir
103+
volumes: *api-volumes
104+
configs: *api-configs
105+
entrypoint: *api-entrypoint
106+
command: *api-command
107+
healthcheck: *api-healthcheck
108+
109+
api-postgres:
110+
profiles: ["postgres"]
111+
depends_on: { postgres: { condition: service_healthy } }
112+
hostname: api
113+
environment:
114+
MEMOS_DSN: "postgresql://memos:memos@postgres:5432/memos?sslmode=disable"
115+
MEMOS_DRIVER: postgres
116+
image: *api-image
117+
ports: *api-ports
118+
working_dir: *api-working-dir
119+
volumes: *api-volumes
120+
configs: *api-configs
121+
entrypoint: *api-entrypoint
122+
command: *api-command
123+
healthcheck: *api-healthcheck
124+
125+
mysql:
126+
profiles: ["mysql"]
127+
image: mysql
128+
environment:
129+
MYSQL_USER: memos
130+
MYSQL_PASSWORD: memos
131+
MYSQL_DATABASE: memos
132+
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
133+
volumes: [../.air/docker/mysql:/var/lib/mysql]
134+
healthcheck:
135+
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
136+
interval: 10s
137+
timeout: 5s
138+
139+
postgres:
140+
profiles: ["postgres"]
141+
image: postgres:alpine
142+
hostname: postgres
143+
volumes: [../.air/docker/postgres:/var/lib/postgresql/data]
144+
environment:
145+
{ POSTGRES_DB: memos, POSTGRES_USER: memos, POSTGRES_PASSWORD: memos }
146+
healthcheck:
147+
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
148+
interval: 10s
149+
timeout: 5s
150+
151+
pnpm:
152+
profiles: ["tools"]
153+
image: node:20-alpine
154+
environment: { NPM_CONFIG_UPDATE_NOTIFIER: false }
155+
working_dir: *web-working-dir
156+
volumes: *web-volumes
157+
tmpfs: *web-tmpfs
158+
configs:
159+
- source: pnpm-entrypoint.sh
160+
target: /usr/local/bin/entrypoint.sh
161+
entrypoint: ["sh", "/usr/local/bin/entrypoint.sh"]
43162

44-
# Generate typescript code of gRPC proto
45163
buf:
46164
profiles: ["tools"]
47165
image: bufbuild/buf
48166
working_dir: /work/proto
49167
command: generate
50168
volumes:
51-
- ./../proto:/work/proto
52-
- ./../web/src/types/:/work/web/src/types/
169+
- ../proto:/work/proto
170+
- ../web/src/types/:/work/web/src/types/
53171

54-
# Do golang static code check before create PR
55-
golangci-lint:
172+
go:
56173
profiles: ["tools"]
57-
image: golangci/golangci-lint:v1.54.2
58-
working_dir: /work/
59-
entrypoint: golangci-lint
60-
command: run -v
61-
volumes:
62-
- $HOME/go/pkg/:/go/pkg/ # Cache for go mod shared with the host
63-
- ./../.air/go-build:/root/.cache/go-build
64-
- ./..:/work/
174+
image: *api-image
175+
working_dir: *api-working-dir
176+
volumes: *api-volumes
177+
entrypoint: ["go"]
65178

66-
# run npm
67-
npm:
179+
goimports:
68180
profiles: ["tools"]
69-
image: node:20-alpine
70-
working_dir: /work
71-
environment: ["NPM_CONFIG_UPDATE_NOTIFIER=false"]
72-
entrypoint: "npm"
73-
volumes:
74-
- ./../web:/work
181+
image: *api-image
182+
working_dir: *api-working-dir
183+
volumes: *api-volumes
184+
configs:
185+
- source: goimports-entrypoint.sh
186+
target: /usr/local/bin/entrypoint.sh
187+
entrypoint: ["/bin/sh", "/usr/local/bin/entrypoint.sh"]
188+
189+
golangci-lint:
190+
profiles: ["tools"]
191+
image: *api-image
192+
working_dir: *api-working-dir
193+
volumes: *api-volumes
194+
configs:
195+
- source: golangci-lint-entrypoint.sh
196+
target: /usr/local/bin/entrypoint.sh
197+
entrypoint: ["/bin/sh", "/usr/local/bin/entrypoint.sh"]
198+
199+
adminer-mysql:
200+
profiles: ["mysql"]
201+
depends_on: { mysql: { condition: service_healthy } }
202+
image: adminer
203+
environment: &adminer-environment
204+
ADMINER_DEFAULT_DRIVER: server # "server" is mysql
205+
ADMINER_DEFAULT_SERVER: mysql
206+
ADMINER_DEFAULT_USERNAME: memos
207+
ADMINER_DEFAULT_PASSWORD: memos
208+
ADMINER_DEFAULT_DB: memos
209+
ADMINER_DESIGN: dracula # light: pepa-linha | https://www.adminer.org/#extras
210+
ADMINER_PLUGINS: tables-filter table-structure edit-textarea dump-json # https://www.adminer.org/en/plugins/
211+
ports: &adminer-ports [127.0.0.1:8091:8080]
212+
healthcheck: &adminer-healthcheck
213+
test: 'php -r "exit(strpos(file_get_contents(\"http://localhost:8080/\"), \"Adminer\") !== false ? 0 : 1);"'
214+
interval: 10s
215+
timeout: 5s
216+
configs: &adminer-configs
217+
- source: adminer-index.php
218+
target: /var/www/html/index.php
219+
220+
adminer-postgres:
221+
profiles: ["postgres"]
222+
depends_on: { postgres: { condition: service_healthy } }
223+
image: adminer
224+
ports: *adminer-ports
225+
healthcheck: *adminer-healthcheck
226+
configs: *adminer-configs
227+
environment:
228+
<<: *adminer-environment
229+
ADMINER_DEFAULT_DRIVER: pgsql
230+
ADMINER_DEFAULT_SERVER: postgres
231+
232+
adminer-sqlite:
233+
profiles: ["sqlite"]
234+
image: adminer
235+
ports: *adminer-ports
236+
healthcheck: *adminer-healthcheck
237+
configs: *adminer-configs
238+
environment:
239+
<<: *adminer-environment
240+
ADMINER_DEFAULT_PASSWORD: ""
241+
ADMINER_DEFAULT_DRIVER: sqlite
242+
ADMINER_DEFAULT_DB: /data/memos_dev.db
243+
volumes: [../.air/docker/memosdata:/data]
244+
245+
configs:
246+
# Patched version of adminer index.php to fill the login form with default values
247+
# and allow passwordless login whenever ADMINER_DEFAULT_DRIVER is sqlite.
248+
adminer-index.php:
249+
content: |
250+
<?php
251+
namespace docker {
252+
function adminer_object() {
253+
require_once('plugins/plugin.php');
254+
class Adminer extends \AdminerPlugin {
255+
function _callParent($$function, $$args) {
256+
if ($$function === 'loginForm') {
257+
ob_start();
258+
$$return = \Adminer::loginForm();
259+
$$form = ob_get_clean();
260+
$$driver = $$_ENV["ADMINER_DEFAULT_DRIVER"] ?: "server";
261+
$$server = $$_ENV["ADMINER_DEFAULT_SERVER"] ?: "db";
262+
$$username = $$_ENV["ADMINER_DEFAULT_USERNAME"];
263+
$$password = $$_ENV["ADMINER_DEFAULT_PASSWORD"];
264+
$$db = $$_ENV["ADMINER_DEFAULT_DB"];
265+
$$form = preg_replace('/ name="auth\[server\]" value="(.*)"/', ' name="auth[server]" value="' . $$server . '"', $$form);
266+
$$form = str_replace(' id="username" value="" ', ' id="username" value="' . $$username . '" ', $$form);
267+
$$form = str_replace('name="auth[db]" value=""', 'name="auth[db]" value="' . $$db . '" ', $$form);
268+
$$form = str_replace('type="password"', 'type="password" value="' . $$password . '"', $$form);
269+
$$form = preg_replace('/<option value="(.*)" selected/', '/<option value="$$1"/', $$form);
270+
$$form = preg_replace('/<option value="' . $$driver . '"/', '<option value="' . $$driver . '" selected', $$form);
271+
echo $$form;
272+
return $$return;
273+
}
274+
return parent::_callParent($$function, $$args);
275+
}
276+
}
277+
$$plugins = [];
278+
foreach (glob('plugins-enabled/*.php') as $$plugin) {
279+
$$plugins[] = require($$plugin);
280+
}
281+
class AdminerSoftware extends Adminer {
282+
function login($$login, $$password) {
283+
return substr($$_ENV["ADMINER_DEFAULT_DRIVER"], 0, 6) == 'sqlite' ? true : parent::login($$login, $$password);
284+
}
285+
}
286+
return new AdminerSoftware($$plugins);
287+
}
288+
}
289+
namespace {
290+
if (basename($$_SERVER['DOCUMENT_URI'] ?? $$_SERVER['REQUEST_URI']) === 'adminer.css' && is_readable('adminer.css')) {
291+
header('Content-Type: text/css');
292+
readfile('adminer.css');
293+
exit;
294+
}
295+
function adminer_object() {
296+
return \docker\adminer_object();
297+
}
298+
require('adminer.php');
299+
}
300+
# Patched version of node's container entrypoint to run commands with pnpm by default.
301+
pnpm-entrypoint.sh:
302+
content: |
303+
set -eu
304+
corepack enable pnpm
305+
pnpm "$$@"
306+
# Entrypoint for air container. Installs air and run passed commands.
307+
air-entrypoint.sh:
308+
content: |
309+
set -eu
310+
if [ -z $$(command -v "air") ]; then
311+
echo "Installing air..."
312+
wget -O- -nv https://raw.githubusercontent.com/cosmtrek/air/master/install.sh | sh -s -- -b \
313+
$$(go env GOPATH)/bin v1.49.0
314+
fi
315+
cd /work
316+
/go/bin/air "$$@"
317+
# Entrypoint for goimports container.
318+
goimports-entrypoint.sh:
319+
content: |
320+
set -eu
321+
if [ -z $$(command -v "goimports") ]; then
322+
echo "Installing goimports..."
323+
go install golang.org/x/tools/cmd/goimports@latest
324+
fi
325+
cd /work
326+
echo "Running goimports..."
327+
goimports "$$@"
328+
# Entrypoint for golangci-lint container.
329+
golangci-lint-entrypoint.sh:
330+
content: |
331+
set -eu
332+
if [ -z $$(command -v "golangci-lint") ]; then
333+
echo "Installing golangci-lint..."
334+
wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b \
335+
$$(go env GOPATH)/bin v1.55.2
336+
fi
337+
cd /work
338+
golangci-lint --version
339+
golangci-lint "$$@"

0 commit comments

Comments
 (0)