docker-compose.dev.yaml 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  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. #
  41. version: "3.0"
  42. 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:
  48. services:
  49. web:
  50. profiles: ["sqlite", "mysql", "postgres"]
  51. image: node:20-alpine
  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
  57. entrypoint: ["/bin/sh", "-c"]
  58. command: ["corepack enable && pnpm i --frozen-lockfile && pnpm dev"]
  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. - ../web/node_modules:/work/web/node_modules
  66. healthcheck:
  67. test: ["CMD", "wget", "-qO", "-", "http://localhost:3001"]
  68. interval: 10s
  69. timeout: 5s
  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
  93. api-mysql:
  94. profiles: ["mysql"]
  95. depends_on: { mysql: { condition: service_healthy } }
  96. hostname: api
  97. environment:
  98. { MEMOS_DRIVER: mysql, MEMOS_DSN: memos:memos@tcp(mysql)/memos }
  99. image: *api-image
  100. ports: *api-ports
  101. working_dir: *api-working-dir
  102. volumes: *api-volumes
  103. configs: *api-configs
  104. entrypoint: *api-entrypoint
  105. command: *api-command
  106. healthcheck: *api-healthcheck
  107. api-postgres:
  108. profiles: ["postgres"]
  109. depends_on: { postgres: { condition: service_healthy } }
  110. hostname: api
  111. environment:
  112. MEMOS_DSN: "postgresql://memos:memos@postgres:5432/memos?sslmode=disable"
  113. MEMOS_DRIVER: postgres
  114. image: *api-image
  115. ports: *api-ports
  116. working_dir: *api-working-dir
  117. volumes: *api-volumes
  118. configs: *api-configs
  119. entrypoint: *api-entrypoint
  120. command: *api-command
  121. healthcheck: *api-healthcheck
  122. mysql:
  123. profiles: ["mysql"]
  124. image: mysql
  125. environment:
  126. MYSQL_USER: memos
  127. MYSQL_PASSWORD: memos
  128. MYSQL_DATABASE: memos
  129. MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
  130. volumes: [../.air/docker/mysql:/var/lib/mysql]
  131. healthcheck:
  132. test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
  133. interval: 10s
  134. timeout: 5s
  135. postgres:
  136. profiles: ["postgres"]
  137. image: postgres:alpine
  138. hostname: postgres
  139. volumes: [../.air/docker/postgres:/var/lib/postgresql/data]
  140. environment:
  141. { POSTGRES_DB: memos, POSTGRES_USER: memos, POSTGRES_PASSWORD: memos }
  142. healthcheck:
  143. test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
  144. interval: 10s
  145. timeout: 5s
  146. pnpm:
  147. profiles: ["tools"]
  148. image: node:20-alpine
  149. environment: { NPM_CONFIG_UPDATE_NOTIFIER: false }
  150. working_dir: *web-working-dir
  151. volumes: *web-volumes
  152. tmpfs: *web-tmpfs
  153. configs:
  154. - source: pnpm-entrypoint.sh
  155. target: /usr/local/bin/entrypoint.sh
  156. entrypoint: ["sh", "/usr/local/bin/entrypoint.sh"]
  157. buf:
  158. profiles: ["tools"]
  159. image: bufbuild/buf
  160. working_dir: /work/proto
  161. command: generate
  162. volumes:
  163. - ../proto:/work/proto
  164. - ../web/src/types/:/work/web/src/types/
  165. go:
  166. profiles: ["tools"]
  167. image: *api-image
  168. working_dir: *api-working-dir
  169. volumes: *api-volumes
  170. entrypoint: ["go"]
  171. goimports:
  172. profiles: ["tools"]
  173. image: *api-image
  174. working_dir: *api-working-dir
  175. volumes: *api-volumes
  176. configs:
  177. - source: goimports-entrypoint.sh
  178. target: /usr/local/bin/entrypoint.sh
  179. entrypoint: ["/bin/sh", "/usr/local/bin/entrypoint.sh"]
  180. golangci-lint:
  181. profiles: ["tools"]
  182. image: *api-image
  183. working_dir: *api-working-dir
  184. volumes: *api-volumes
  185. configs:
  186. - source: golangci-lint-entrypoint.sh
  187. target: /usr/local/bin/entrypoint.sh
  188. entrypoint: ["/bin/sh", "/usr/local/bin/entrypoint.sh"]
  189. adminer-mysql:
  190. profiles: ["mysql"]
  191. depends_on: { mysql: { condition: service_healthy } }
  192. image: adminer
  193. environment: &adminer-environment
  194. ADMINER_DEFAULT_DRIVER: server # "server" is mysql
  195. ADMINER_DEFAULT_SERVER: mysql
  196. ADMINER_DEFAULT_USERNAME: memos
  197. ADMINER_DEFAULT_PASSWORD: memos
  198. ADMINER_DEFAULT_DB: memos
  199. ADMINER_DESIGN: dracula # light: pepa-linha | https://www.adminer.org/#extras
  200. ADMINER_PLUGINS: tables-filter table-structure edit-textarea dump-json # https://www.adminer.org/en/plugins/
  201. ports: &adminer-ports [127.0.0.1:8091:8080]
  202. healthcheck: &adminer-healthcheck
  203. test: 'php -r "exit(strpos(file_get_contents(\"http://localhost:8080/\"), \"Adminer\") !== false ? 0 : 1);"'
  204. interval: 10s
  205. timeout: 5s
  206. configs: &adminer-configs
  207. - source: adminer-index.php
  208. target: /var/www/html/index.php
  209. adminer-postgres:
  210. profiles: ["postgres"]
  211. depends_on: { postgres: { condition: service_healthy } }
  212. image: adminer
  213. ports: *adminer-ports
  214. healthcheck: *adminer-healthcheck
  215. configs: *adminer-configs
  216. environment:
  217. <<: *adminer-environment
  218. ADMINER_DEFAULT_DRIVER: pgsql
  219. ADMINER_DEFAULT_SERVER: postgres
  220. adminer-sqlite:
  221. profiles: ["sqlite"]
  222. image: adminer
  223. ports: *adminer-ports
  224. healthcheck: *adminer-healthcheck
  225. configs: *adminer-configs
  226. environment:
  227. <<: *adminer-environment
  228. ADMINER_DEFAULT_PASSWORD: ""
  229. ADMINER_DEFAULT_DRIVER: sqlite
  230. ADMINER_DEFAULT_DB: /data/memos_dev.db
  231. volumes: [../.air/docker/memosdata:/data]
  232. configs:
  233. # Patched version of adminer index.php to fill the login form with default values
  234. # and allow passwordless login whenever ADMINER_DEFAULT_DRIVER is sqlite.
  235. adminer-index.php:
  236. content: |
  237. <?php
  238. namespace docker {
  239. function adminer_object() {
  240. require_once('plugins/plugin.php');
  241. class Adminer extends \AdminerPlugin {
  242. function _callParent($$function, $$args) {
  243. if ($$function === 'loginForm') {
  244. ob_start();
  245. $$return = \Adminer::loginForm();
  246. $$form = ob_get_clean();
  247. $$driver = $$_ENV["ADMINER_DEFAULT_DRIVER"] ?: "server";
  248. $$server = $$_ENV["ADMINER_DEFAULT_SERVER"] ?: "db";
  249. $$username = $$_ENV["ADMINER_DEFAULT_USERNAME"];
  250. $$password = $$_ENV["ADMINER_DEFAULT_PASSWORD"];
  251. $$db = $$_ENV["ADMINER_DEFAULT_DB"];
  252. $$form = preg_replace('/ name="auth\[server\]" value="(.*)"/', ' name="auth[server]" value="' . $$server . '"', $$form);
  253. $$form = str_replace(' id="username" value="" ', ' id="username" value="' . $$username . '" ', $$form);
  254. $$form = str_replace('name="auth[db]" value=""', 'name="auth[db]" value="' . $$db . '" ', $$form);
  255. $$form = str_replace('type="password"', 'type="password" value="' . $$password . '"', $$form);
  256. $$form = preg_replace('/<option value="(.*)" selected/', '/<option value="$$1"/', $$form);
  257. $$form = preg_replace('/<option value="' . $$driver . '"/', '<option value="' . $$driver . '" selected', $$form);
  258. echo $$form;
  259. return $$return;
  260. }
  261. return parent::_callParent($$function, $$args);
  262. }
  263. }
  264. $$plugins = [];
  265. foreach (glob('plugins-enabled/*.php') as $$plugin) {
  266. $$plugins[] = require($$plugin);
  267. }
  268. class AdminerSoftware extends Adminer {
  269. function login($$login, $$password) {
  270. return substr($$_ENV["ADMINER_DEFAULT_DRIVER"], 0, 6) == 'sqlite' ? true : parent::login($$login, $$password);
  271. }
  272. }
  273. return new AdminerSoftware($$plugins);
  274. }
  275. }
  276. namespace {
  277. if (basename($$_SERVER['DOCUMENT_URI'] ?? $$_SERVER['REQUEST_URI']) === 'adminer.css' && is_readable('adminer.css')) {
  278. header('Content-Type: text/css');
  279. readfile('adminer.css');
  280. exit;
  281. }
  282. function adminer_object() {
  283. return \docker\adminer_object();
  284. }
  285. require('adminer.php');
  286. }
  287. # Patched version of node's container entrypoint to run commands with pnpm by default.
  288. pnpm-entrypoint.sh:
  289. content: |
  290. set -eu
  291. corepack enable pnpm
  292. pnpm "$$@"
  293. # Entrypoint for air container. Installs air and run passed commands.
  294. air-entrypoint.sh:
  295. content: |
  296. set -eu
  297. if [ -z $$(command -v "air") ]; then
  298. echo "Installing air..."
  299. wget -O- -nv https://raw.githubusercontent.com/cosmtrek/air/master/install.sh | sh -s -- -b \
  300. $$(go env GOPATH)/bin v1.49.0
  301. fi
  302. cd /work
  303. /go/bin/air "$$@"
  304. # Entrypoint for goimports container.
  305. goimports-entrypoint.sh:
  306. content: |
  307. set -eu
  308. if [ -z $$(command -v "goimports") ]; then
  309. echo "Installing goimports..."
  310. go install golang.org/x/tools/cmd/goimports@latest
  311. fi
  312. cd /work
  313. echo "Running goimports..."
  314. goimports "$$@"
  315. # Entrypoint for golangci-lint container.
  316. golangci-lint-entrypoint.sh:
  317. content: |
  318. set -eu
  319. if [ -z $$(command -v "golangci-lint") ]; then
  320. echo "Installing golangci-lint..."
  321. wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b \
  322. $$(go env GOPATH)/bin v1.55.2
  323. fi
  324. cd /work
  325. golangci-lint --version
  326. golangci-lint "$$@"