# > 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 "$$@"