123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- # > Memos development environment <
- #
- # Available profiles: sqlite, mysql, postgres.
- # Use `docker compose --profile PROFILE_NAME up` to launch only services within the profile.
- #
- # Services in the `tools` profile are used for running one-off tasks like linting, generating code, etc.
- #
- # Services started in all database profiles:
- # Front-end: http://localhost:3001
- # API: http://localhost:8081
- # Adminer: http://localhost:8091
- #
- # On Windows, run this before using docker-compose on a new terminal:
- # $Env:HOME=$Env:USERPROFILE
- #
- # > Start Memos in development mode:
- # docker compose -f ./scripts/docker-compose.dev.yaml --profile [sqlite|mysql|postgres] up --detach
- #
- # > Stop all services:
- # docker compose -f ./scripts/docker-compose.dev.yaml --profile sqlite --profile postgres --profile mysql down
- #
- # > Remove related volumes: (all other files are mapped to ./air/docker/ directory)
- # docker volume rm memos-dev_pnpm-store memos-dev_node-modules
- #
- # One-off tasks:
- # > pnpm:
- # docker compose -f ./scripts/docker-compose.dev.yaml run --rm pnpm [add|remove|update] [PACKAGE_NAME] [--save-dev]
- #
- # > buf: (run this after modifying .proto files)
- # docker compose -f ./scripts/docker-compose.dev.yaml run --rm buf generate
- #
- # > go:
- # docker compose -f ./scripts/docker-compose.dev.yaml run --rm go mod tidy -go=1.22
- #
- # > golangci-lint: (run this before submitting Pull Requests affecting Go code)
- # docker compose -f ./scripts/docker-compose.dev.yaml run --rm golangci-lint run
- #
- # > goimports: (run this if golangci-lint shows "File is not `goimports`-ed"
- # docker compose -f ./scripts/docker-compose.dev.yaml run --rm goimports -local https://github.com/usememos/memos -w [FILE|.]
- #
- version: "3.0"
- name: memos-dev
- volumes:
- # pnpm uses hard links and node_modules uses symlinks.
- # Using volumes make things work properly on any host OS.
- node-modules:
- pnpm-store:
- services:
- web:
- profiles: ["sqlite", "mysql", "postgres"]
- image: node:20-alpine
- ports: [3001:3001]
- environment:
- DEV_PROXY_SERVER: http://api:8081/
- NPM_CONFIG_UPDATE_NOTIFIER: false
- working_dir: &web-working-dir /work/web
- entrypoint: ["/bin/sh", "-c"]
- command: ["corepack enable && pnpm i --frozen-lockfile && pnpm dev"]
- tmpfs: &web-tmpfs /work/node_modules/:exec # avoid ERR_PNPM_LINKING_FAILED
- volumes: &web-volumes
- - node-modules:/work/web/node_modules
- - pnpm-store:/work/web/.pnpm-store
- - ../proto:/work/proto
- - ../web:/work/web
- - ../web/node_modules:/work/web/node_modules
- healthcheck:
- test: ["CMD", "wget", "-qO", "-", "http://localhost:3001"]
- interval: 10s
- timeout: 5s
- api:
- profiles: ["sqlite"]
- image: &api-image golang:1.22-alpine
- ports: &api-ports [8081:8081]
- environment:
- MEMOS_DRIVER: sqlite
- MEMOS_DATA: /var/opt/memos
- working_dir: &api-working-dir /work
- volumes: &api-volumes
- - $HOME/go/pkg/:/go/pkg/ # Share go mod cache with host
- - ../.air/docker/go-build:/root/.cache/go-build
- - ../.air/docker/go/bin:/go/bin
- - ../.air/docker/memosdata:/var/opt/memos
- - ..:/work/
- configs: &api-configs
- - source: air-entrypoint.sh
- target: /usr/local/bin/entrypoint.sh
- entrypoint: &api-entrypoint ["/bin/sh", "/usr/local/bin/entrypoint.sh"]
- command: &api-command ["-c", "./scripts/.air.toml"]
- healthcheck: &api-healthcheck
- test: ["CMD", "wget", "-qO", "-", "http://localhost:8081/api/v1/ping"]
- interval: 10s
- timeout: 5s
- api-mysql:
- profiles: ["mysql"]
- depends_on: { mysql: { condition: service_healthy } }
- hostname: api
- environment:
- { MEMOS_DRIVER: mysql, MEMOS_DSN: memos:memos@tcp(mysql)/memos }
- image: *api-image
- ports: *api-ports
- working_dir: *api-working-dir
- volumes: *api-volumes
- configs: *api-configs
- entrypoint: *api-entrypoint
- command: *api-command
- healthcheck: *api-healthcheck
- api-postgres:
- profiles: ["postgres"]
- depends_on: { postgres: { condition: service_healthy } }
- hostname: api
- environment:
- MEMOS_DSN: "postgresql://memos:memos@postgres:5432/memos?sslmode=disable"
- MEMOS_DRIVER: postgres
- image: *api-image
- ports: *api-ports
- working_dir: *api-working-dir
- volumes: *api-volumes
- configs: *api-configs
- entrypoint: *api-entrypoint
- command: *api-command
- healthcheck: *api-healthcheck
- mysql:
- profiles: ["mysql"]
- image: mysql
- environment:
- MYSQL_USER: memos
- MYSQL_PASSWORD: memos
- MYSQL_DATABASE: memos
- MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
- volumes: [../.air/docker/mysql:/var/lib/mysql]
- healthcheck:
- test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
- interval: 10s
- timeout: 5s
- postgres:
- profiles: ["postgres"]
- image: postgres:alpine
- hostname: postgres
- volumes: [../.air/docker/postgres:/var/lib/postgresql/data]
- environment:
- { POSTGRES_DB: memos, POSTGRES_USER: memos, POSTGRES_PASSWORD: memos }
- healthcheck:
- test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
- interval: 10s
- timeout: 5s
- pnpm:
- profiles: ["tools"]
- image: node:20-alpine
- environment: { NPM_CONFIG_UPDATE_NOTIFIER: false }
- working_dir: *web-working-dir
- volumes: *web-volumes
- tmpfs: *web-tmpfs
- configs:
- - source: pnpm-entrypoint.sh
- target: /usr/local/bin/entrypoint.sh
- entrypoint: ["sh", "/usr/local/bin/entrypoint.sh"]
- buf:
- profiles: ["tools"]
- image: bufbuild/buf
- working_dir: /work/proto
- command: generate
- volumes:
- - ../proto:/work/proto
- - ../web/src/types/:/work/web/src/types/
- go:
- profiles: ["tools"]
- image: *api-image
- working_dir: *api-working-dir
- volumes: *api-volumes
- entrypoint: ["go"]
- goimports:
- profiles: ["tools"]
- image: *api-image
- working_dir: *api-working-dir
- volumes: *api-volumes
- configs:
- - source: goimports-entrypoint.sh
- target: /usr/local/bin/entrypoint.sh
- entrypoint: ["/bin/sh", "/usr/local/bin/entrypoint.sh"]
- golangci-lint:
- profiles: ["tools"]
- image: *api-image
- working_dir: *api-working-dir
- volumes: *api-volumes
- configs:
- - source: golangci-lint-entrypoint.sh
- target: /usr/local/bin/entrypoint.sh
- entrypoint: ["/bin/sh", "/usr/local/bin/entrypoint.sh"]
- adminer-mysql:
- profiles: ["mysql"]
- depends_on: { mysql: { condition: service_healthy } }
- image: adminer
- environment: &adminer-environment
- ADMINER_DEFAULT_DRIVER: server # "server" is mysql
- ADMINER_DEFAULT_SERVER: mysql
- ADMINER_DEFAULT_USERNAME: memos
- ADMINER_DEFAULT_PASSWORD: memos
- ADMINER_DEFAULT_DB: memos
- ADMINER_DESIGN: dracula # light: pepa-linha | https://www.adminer.org/#extras
- ADMINER_PLUGINS: tables-filter table-structure edit-textarea dump-json # https://www.adminer.org/en/plugins/
- ports: &adminer-ports [127.0.0.1:8091:8080]
- healthcheck: &adminer-healthcheck
- test: 'php -r "exit(strpos(file_get_contents(\"http://localhost:8080/\"), \"Adminer\") !== false ? 0 : 1);"'
- interval: 10s
- timeout: 5s
- configs: &adminer-configs
- - source: adminer-index.php
- target: /var/www/html/index.php
- adminer-postgres:
- profiles: ["postgres"]
- depends_on: { postgres: { condition: service_healthy } }
- image: adminer
- ports: *adminer-ports
- healthcheck: *adminer-healthcheck
- configs: *adminer-configs
- environment:
- <<: *adminer-environment
- ADMINER_DEFAULT_DRIVER: pgsql
- ADMINER_DEFAULT_SERVER: postgres
- adminer-sqlite:
- profiles: ["sqlite"]
- image: adminer
- ports: *adminer-ports
- healthcheck: *adminer-healthcheck
- configs: *adminer-configs
- environment:
- <<: *adminer-environment
- ADMINER_DEFAULT_PASSWORD: ""
- ADMINER_DEFAULT_DRIVER: sqlite
- ADMINER_DEFAULT_DB: /data/memos_dev.db
- volumes: [../.air/docker/memosdata:/data]
- configs:
- # Patched version of adminer index.php to fill the login form with default values
- # and allow passwordless login whenever ADMINER_DEFAULT_DRIVER is sqlite.
- adminer-index.php:
- content: |
- <?php
- namespace docker {
- function adminer_object() {
- require_once('plugins/plugin.php');
- class Adminer extends \AdminerPlugin {
- function _callParent($$function, $$args) {
- if ($$function === 'loginForm') {
- ob_start();
- $$return = \Adminer::loginForm();
- $$form = ob_get_clean();
- $$driver = $$_ENV["ADMINER_DEFAULT_DRIVER"] ?: "server";
- $$server = $$_ENV["ADMINER_DEFAULT_SERVER"] ?: "db";
- $$username = $$_ENV["ADMINER_DEFAULT_USERNAME"];
- $$password = $$_ENV["ADMINER_DEFAULT_PASSWORD"];
- $$db = $$_ENV["ADMINER_DEFAULT_DB"];
- $$form = preg_replace('/ name="auth\[server\]" value="(.*)"/', ' name="auth[server]" value="' . $$server . '"', $$form);
- $$form = str_replace(' id="username" value="" ', ' id="username" value="' . $$username . '" ', $$form);
- $$form = str_replace('name="auth[db]" value=""', 'name="auth[db]" value="' . $$db . '" ', $$form);
- $$form = str_replace('type="password"', 'type="password" value="' . $$password . '"', $$form);
- $$form = preg_replace('/<option value="(.*)" selected/', '/<option value="$$1"/', $$form);
- $$form = preg_replace('/<option value="' . $$driver . '"/', '<option value="' . $$driver . '" selected', $$form);
- echo $$form;
- return $$return;
- }
- return parent::_callParent($$function, $$args);
- }
- }
- $$plugins = [];
- foreach (glob('plugins-enabled/*.php') as $$plugin) {
- $$plugins[] = require($$plugin);
- }
- class AdminerSoftware extends Adminer {
- function login($$login, $$password) {
- return substr($$_ENV["ADMINER_DEFAULT_DRIVER"], 0, 6) == 'sqlite' ? true : parent::login($$login, $$password);
- }
- }
- return new AdminerSoftware($$plugins);
- }
- }
- namespace {
- if (basename($$_SERVER['DOCUMENT_URI'] ?? $$_SERVER['REQUEST_URI']) === 'adminer.css' && is_readable('adminer.css')) {
- header('Content-Type: text/css');
- readfile('adminer.css');
- exit;
- }
- function adminer_object() {
- return \docker\adminer_object();
- }
- require('adminer.php');
- }
- # Patched version of node's container entrypoint to run commands with pnpm by default.
- pnpm-entrypoint.sh:
- content: |
- set -eu
- corepack enable pnpm
- pnpm "$$@"
- # Entrypoint for air container. Installs air and run passed commands.
- air-entrypoint.sh:
- content: |
- set -eu
- if [ -z $$(command -v "air") ]; then
- echo "Installing air..."
- wget -O- -nv https://raw.githubusercontent.com/cosmtrek/air/master/install.sh | sh -s -- -b \
- $$(go env GOPATH)/bin v1.49.0
- fi
- cd /work
- /go/bin/air "$$@"
- # Entrypoint for goimports container.
- goimports-entrypoint.sh:
- content: |
- set -eu
- if [ -z $$(command -v "goimports") ]; then
- echo "Installing goimports..."
- go install golang.org/x/tools/cmd/goimports@latest
- fi
- cd /work
- echo "Running goimports..."
- goimports "$$@"
- # Entrypoint for golangci-lint container.
- golangci-lint-entrypoint.sh:
- content: |
- set -eu
- if [ -z $$(command -v "golangci-lint") ]; then
- echo "Installing golangci-lint..."
- wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b \
- $$(go env GOPATH)/bin v1.55.2
- fi
- cd /work
- golangci-lint --version
- golangci-lint "$$@"
|