Docker Compose is recommended for production deployments. It provides a declarative configuration and makes it easy to manage services.
Basic Setup
The official compose file is at scripts/compose.yaml:1-9.
Create docker-compose.yml
services:
memos:
image: neosmemo/memos:stable
container_name: memos
volumes:
- ~/.memos:/var/opt/memos
ports:
- 5230:5230
Start Memos
View Logs
Stop Memos
Production Configuration
A more complete production setup with custom settings:
services:
memos:
image: neosmemo/memos:stable
container_name: memos
restart: unless-stopped
volumes:
- ./data:/var/opt/memos
ports:
- "5230:5230"
environment:
# Server configuration
MEMOS_PORT: 5230
MEMOS_DRIVER: sqlite
MEMOS_INSTANCE_URL: https://memos.example.com
# Security
security_opt:
- no-new-privileges:true
# Resource limits
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
# Health check
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:5230/healthz"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
With MySQL
Run Memos with a MySQL database:
services:
memos:
image: neosmemo/memos:stable
container_name: memos
restart: unless-stopped
depends_on:
mysql:
condition: service_healthy
volumes:
- ./attachments:/var/opt/memos
ports:
- "5230:5230"
environment:
MEMOS_DRIVER: mysql
MEMOS_DSN: memos:memos@tcp(mysql:3306)/memos
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:5230/healthz"]
interval: 30s
timeout: 10s
retries: 3
mysql:
image: mysql:8.4
container_name: memos-mysql
restart: unless-stopped
volumes:
- ./mysql-data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: root_password_change_me
MYSQL_DATABASE: memos
MYSQL_USER: memos
MYSQL_PASSWORD: memos
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p$$MYSQL_ROOT_PASSWORD"]
interval: 10s
timeout: 5s
retries: 5
Change the default passwords before deploying to production!
With PostgreSQL
Run Memos with a PostgreSQL database:
services:
memos:
image: neosmemo/memos:stable
container_name: memos
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
volumes:
- ./attachments:/var/opt/memos
ports:
- "5230:5230"
environment:
MEMOS_DRIVER: postgres
MEMOS_DSN: postgres://memos:memos@postgres:5432/memos?sslmode=disable
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:5230/healthz"]
interval: 30s
timeout: 10s
retries: 3
postgres:
image: postgres:16-alpine
container_name: memos-postgres
restart: unless-stopped
volumes:
- ./postgres-data:/var/lib/postgresql/data
environment:
POSTGRES_DB: memos
POSTGRES_USER: memos
POSTGRES_PASSWORD: memos
PGDATA: /var/lib/postgresql/data/pgdata
healthcheck:
test: ["CMD-SHELL", "pg_isready -U memos"]
interval: 10s
timeout: 5s
retries: 5
With Reverse Proxy (Caddy)
Add Caddy as a reverse proxy with automatic HTTPS:
services:
memos:
image: neosmemo/memos:stable
container_name: memos
restart: unless-stopped
volumes:
- ./data:/var/opt/memos
# Don't expose port directly, use Caddy instead
expose:
- 5230
environment:
MEMOS_PORT: 5230
MEMOS_INSTANCE_URL: https://memos.example.com
networks:
- memos-network
caddy:
image: caddy:2-alpine
container_name: memos-caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp" # HTTP/3
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./caddy-data:/data
- ./caddy-config:/config
networks:
- memos-network
networks:
memos-network:
driver: bridge
Create a Caddyfile:
memos.example.com {
reverse_proxy memos:5230
encode gzip
log {
output file /data/access.log
}
}
With Reverse Proxy (Nginx)
Alternatively, use Nginx as a reverse proxy:
services:
memos:
image: neosmemo/memos:stable
container_name: memos
restart: unless-stopped
volumes:
- ./data:/var/opt/memos
expose:
- 5230
environment:
MEMOS_PORT: 5230
networks:
- memos-network
nginx:
image: nginx:alpine
container_name: memos-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
networks:
- memos-network
networks:
memos-network:
driver: bridge
Create nginx.conf:
events {
worker_connections 1024;
}
http {
upstream memos {
server memos:5230;
}
server {
listen 80;
server_name memos.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name memos.example.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
location / {
proxy_pass http://memos;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
Using Docker Secrets
For secure credential management:
Create secrets
echo "postgres://user:password@postgres:5432/memos" | docker secret create memos_dsn -
Update compose file
services:
memos:
image: neosmemo/memos:stable
secrets:
- memos_dsn
environment:
MEMOS_DRIVER: postgres
MEMOS_DSN_FILE: /run/secrets/memos_dsn
secrets:
memos_dsn:
external: true
The entrypoint script supports *_FILE environment variables for secrets (scripts/entrypoint.sh:17-41).
Backup Strategy
Backup SQLite Database
services:
# ... memos service ...
backup:
image: alpine:3.21
container_name: memos-backup
restart: unless-stopped
volumes:
- ./data:/data:ro
- ./backups:/backups
command: sh -c '
while true; do
timestamp=$$(date +%Y%m%d_%H%M%S)
cp /data/memos.db /backups/memos_$$timestamp.db
find /backups -name "memos_*.db" -mtime +7 -delete
sleep 86400
done
'
Backup with PostgreSQL
services:
# ... memos and postgres services ...
backup:
image: postgres:16-alpine
container_name: memos-backup
restart: unless-stopped
volumes:
- ./backups:/backups
environment:
PGPASSWORD: memos
command: sh -c '
while true; do
timestamp=$$(date +%Y%m%d_%H%M%S)
pg_dump -h postgres -U memos -d memos | gzip > /backups/memos_$$timestamp.sql.gz
find /backups -name "memos_*.sql.gz" -mtime +7 -delete
sleep 86400
done
'
Management Commands
Start services
Stop services
View logs
# All services
docker compose logs -f
# Specific service
docker compose logs -f memos
Restart services
Update Memos
# Pull latest image
docker compose pull memos
# Recreate container
docker compose up -d memos
Execute commands
# Open shell in container
docker compose exec memos sh
# Run memos with different flags
docker compose exec memos memos --help
Troubleshooting
Check service health
View resource usage
Inspect container
docker compose exec memos sh -c 'ls -la /var/opt/memos'
Database connection issues
Ensure the database is ready before Memos starts:
depends_on:
postgres:
condition: service_healthy
The healthcheck configuration ensures the database is accepting connections before Memos tries to connect.
Next Steps