---
title: "How to Install Memos with Docker Compose: EASY STEPS!"
description: "Step-by-step guide to install Memos with Docker Compose using SQLite or PostgreSQL. Self-host a privacy-first note-taking app with Traefik reverse proxy in minutes."
date: 2026-08-04
categories: ["self-hosting"]
tags: ["self-hosted","docker","docker-compose"]
---

import Button from "../../components/widgets/Button.astro";
import { Picture } from "astro:assets";
import YouTubeEmbed from "../../components/widgets/YouTubeEmbed.astro";
import Notice from "../../components/widgets/Notice.astro";
import ListCheck from "../../components/widgets/ListCheck.astro";
import Accordion from "../../components/widgets/Accordion.astro";
import Tabs from "../../components/widgets/Tabs.astro";
import Tab from "../../components/widgets/Tab.astro";
import imag1 from "../../assets/images/24/08/memos1.png";
import imag2 from "../../assets/images/24/08/memos2.jpeg";

[Memos](https://www.usememos.com/) is a self-hosted note-taking app for personal knowledge management. It's open source (MIT license), runs on Go + React, and has over 60,000 GitHub stars as of v0.30.0 (July 2025). It uses SQLite by default, supports Markdown out of the box, and runs comfortably on 1 CPU / 1 GB RAM. This guide walks through installing Memos with Docker Compose using either SQLite or PostgreSQL, behind a Traefik reverse proxy with automatic TLS.

## What is Memos?

Memos is a privacy-first, self-hosted note-taking application. All data stays on your server. There's no cloud dependency, no subscription, no tracking. The interface is clean: you type Markdown, it renders live, and everything syncs across your devices through the web UI.

Core features:

- **Markdown support** with a new CodeMirror 6 editor and formatting toolbar (v0.30.0)
- **Lightweight architecture**: the Go backend and React frontend keep memory usage low
- **Customizable UI**: light/dark themes, server name, icon, description
- **Multi-device access** through any browser
- **Attachments stored on filesystem** by default since v0.27.0 (not inside the database)
- **Open source** under MIT license. Contribute or fork as you like.

If you need something heavier for team documentation, check [How to Install Outline Wiki on Docker](https://www.bitdoze.com/outline-install/) or [Docmost Docker Compose Install](https://www.bitdoze.com/docmost-docker-install/).

Since the original publish, Memos has added several features worth knowing about:

- **Web Clipper** browser extension for [Chrome](https://chromewebstore.google.com/detail/memos-web-clipper/nebaoebnljalfegiidibihhkebeiklbl) and [Firefox](https://addons.mozilla.org/en-US/firefox/addon/memos-web-clipper/)
- **Voice notes with AI transcription** via OpenAI or Gemini providers
- **MCP server** at `/mcp` for AI client integration (Model Context Protocol)
- **CodeMirror 6 editor** with WYSIWYG-style formatting toolbar
- **Multi-column feed layouts**: 1, 2, or 3 columns plus auto-fit
- **SSE live refresh**: real-time updates without polling
- **Standard Webhooks** with HMAC-SHA256 signing
- **Deployment-managed configuration** via `/etc/secrets` JSON files for GitOps workflows

## Install Memos with Docker Compose

Below are two complete setups: SQLite (simplest, recommended for personal use) and PostgreSQL (for when you already run Postgres or want `pg_dump` backups).

<YouTubeEmbed
  url="https://www.youtube.com/embed/vBa4ogLNF14"
  label="Memos Installation"
/>

<Notice type="info" title="Monitor your server">
After deploying Memos, set up server monitoring to keep an eye on CPU, memory, and disk. See [How To Monitor Server and Docker Resources](https://www.bitdoze.com/sever-monitoring/) or set up [Beszel & Uptime Kuma](https://www.bitdoze.com/beszel-uptime-kuma/) for a lightweight dashboard.
</Notice>

### Prerequisites

<ListCheck>
<ul>
  <li>A VPS or home server. Memos runs on 1 CPU / 1 GB RAM without issues. A [Hetzner](https://go.bitdoze.com/hetzner) CX22, [Hostinger](https://go.bitdoze.com/hostinger-vps) KVM VPS, or [Vultr](https://go.bitdoze.com/vultr) instance all work fine. You can also use a [Mini PC as a Home Server](https://www.bitdoze.com/best-mini-pc-home-server/).</li>
  <li>Docker and Docker Compose installed</li>
  <li>A reverse proxy with TLS. Traefik is recommended. See [How to Use Traefik as A Reverse Proxy in Docker](https://www.bitdoze.com/traefik-proxy-docker/) and [Traefik FREE Let's Encrypt Wildcard Certificate](https://www.bitdoze.com/traefik-wildcard-certificate/). You can also use Nginx, Caddy, or Cloudflare Tunnel.</li>
  <li>A domain name pointed to your server</li>
</ul>
</ListCheck>

You can manage Docker Compose stacks through [Dockge](https://www.bitdoze.com/dockge-install/) or any of the [Best Self-Hosted Server Panels](https://www.bitdoze.com/best-self-hosted-panels/).

### Quick start: Memos with SQLite

SQLite is the default backend and the simplest way to get running. One container, one volume, no database service to manage.

```yaml
services:
  memos:
    image: neosmemo/memos:stable
    container_name: memos
    restart: unless-stopped
    networks:
      - traefik-net
    volumes:
      - ./memos:/var/opt/memos
    environment:
      MEMOS_DRIVER: sqlite
      MEMOS_PORT: 5230
      # Set this to your public URL to enable public mode.
      # Leave empty for private-only (no anonymous access, no RSS, no Explore).
      MEMOS_INSTANCE_URL: https://memos.yourdomain.com
      # MEMOS_LOG_LEVEL: debug  # Uncomment for troubleshooting
    labels:
      - traefik.enable=true
      - traefik.http.routers.memos.rule=Host(`memos.yourdomain.com`)
      - traefik.http.routers.memos.entrypoints=websecure
      - traefik.http.routers.memos.tls.certresolver=letsencrypt
      - traefik.http.services.memos.loadbalancer.server.port=5230

networks:
  traefik-net:
    external: true
```

<Notice type="warning" title="v0.30.0 breaking change: private mode default">
Since Memos v0.30.0, leaving `MEMOS_INSTANCE_URL` empty puts the instance in private-only mode. Anonymous visitors get redirected to sign-in, RSS is disabled, and Explore is hidden. Set `MEMOS_INSTANCE_URL` to your public URL (e.g., `https://memos.yourdomain.com`) if you want public memos and RSS feeds.
</Notice>

**What this does:**

- `neosmemo/memos:stable` pulls the latest stable release (currently v0.30.0)
- The volume `./memos:/var/opt/memos` persists all data (database + attachments) on the host
- Traefik labels route `memos.yourdomain.com` to port 5230 with automatic TLS via your cert resolver
- The container runs as non-root by default (UID 10001), no need for `user: root`

If you're not using Traefik (e.g., Cloudflare Tunnel or direct access), replace the `labels` block with a port mapping:

```yaml
ports:
  - 5230:5230
```

Remove the `networks` section if you're not using an external Traefik network.

### Memos with PostgreSQL

Use PostgreSQL if you already run it for other services, want `pg_dump` backup support, or prefer a managed database backend. MySQL is also supported. See the [Memos database docs](https://usememos.com/docs/configuration/database) for connection strings.

<Tabs>
<Tab name="When to use SQLite">
- Zero configuration, just a file on disk
- Ideal for personal, single-node use
- Backup = copy the data directory
- Runs anywhere, no database service to maintain
- Default and recommended for most users
</Tab>
<Tab name="When to use PostgreSQL">
- You already run PostgreSQL for other services
- You want `pg_dump` for structured backups
- You prefer operational control (replication, monitoring)
- You need better concurrency handling for many attachments
- See [Multiple PostgreSQL Databases in ONE Service](https://www.bitdoze.com/multiple-postgres-databases-docker/) to share a single Postgres container
</Tab>
</Tabs>

```yaml
services:
  memos:
    image: neosmemo/memos:stable
    container_name: memos
    restart: unless-stopped
    networks:
      - traefik-net
    depends_on:
      memos-db:
        condition: service_healthy
    volumes:
      - ./memos:/var/opt/memos
    environment:
      MEMOS_DRIVER: postgres
      MEMOS_DSN: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@memos-db:5432/${POSTGRES_DB}?sslmode=disable
      MEMOS_PORT: 5230
      MEMOS_INSTANCE_URL: https://memos.yourdomain.com
    labels:
      - traefik.enable=true
      - traefik.http.routers.memos.rule=Host(`memos.yourdomain.com`)
      - traefik.http.routers.memos.entrypoints=websecure
      - traefik.http.routers.memos.tls.certresolver=letsencrypt
      - traefik.http.services.memos.loadbalancer.server.port=5230

  memos-db:
    image: postgres:16-alpine
    restart: unless-stopped
    networks:
      - traefik-net
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 5s
      timeout: 5s
      retries: 5
    volumes:
      - ./memos-db:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

networks:
  traefik-net:
    external: true
```

The `depends_on` with `condition: service_healthy` ensures Memos doesn't start until PostgreSQL is accepting connections. The PostgreSQL container uses `postgres:16-alpine` for a small image footprint.

### Create the `.env` file

The SQLite setup doesn't need an `.env` file. For PostgreSQL, create a `.env` file in the same directory as your `docker-compose.yml`:

```sh
POSTGRES_DB=memos
POSTGRES_USER=memos
POSTGRES_PASSWORD=change-me-to-a-strong-random-password
```

<Notice type="warning" title="Change the default password">
Generate a random password before deploying. You can use `openssl rand -base64 32` to create one. Don't run PostgreSQL with a guessable password, even on a private network.
</Notice>

### Start Memos

```sh
docker compose up -d
```

This pulls the images, creates the containers, and starts everything in detached mode.

### Verify it works

After `docker compose up -d`, confirm everything is healthy:

```sh
# Check container status — both should be "Up" and (for postgres) "healthy"
docker compose ps

# Check Memos logs for startup confirmation
docker compose logs memos
# Look for: "Server started at http://localhost:5230"

# Quick HTTP check
curl -I http://localhost:5230
# Expect: HTTP/1.1 200 OK
```

If the container is restarting, check the logs with `docker compose logs memos --tail 50` for error messages.

### Access the Memos UI

Open `https://memos.yourdomain.com` in your browser. The first user you create becomes the admin. You'll see the main memo feed:

<Picture src={imag1} alt="Memos self-hosted note-taking app dashboard" />

From the Settings page you can switch between light and dark themes, manage users, configure SSO/OAuth2 (callback URL: `https://<instance>/auth/callback`), and set up storage backends:

<Picture src={imag2} alt="Memos Docker Compose settings page" />

## Environment variables reference

| Variable | Purpose | Default |
|---|---|---|
| `MEMOS_PORT` | HTTP listen port | `5230` |
| `MEMOS_DRIVER` | Database backend: `sqlite`, `postgres`, `mysql` | `sqlite` |
| `MEMOS_DSN` | Database connection string | Auto for SQLite |
| `MEMOS_INSTANCE_URL` | Public URL; empty = private mode | _(empty)_ |
| `MEMOS_UID` / `MEMOS_GID` | Override container UID/GID | `10001` / `10001` |
| `MEMOS_LOG_LEVEL` | `debug`, `info`, `warn`, `error` | `info` |

See the [full environment variables docs](https://usememos.com/docs/configuration/environment-variables) for all options.

## Backup and upgrade Memos

### Backup

Backups depend on your database backend. Don't skip this, especially before upgrades.

**SQLite:**

```sh
# Stop Memos for a consistent snapshot
docker compose stop memos
tar -czf memos-backup-$(date +%Y%m%d).tar.gz ./memos/
docker compose start memos
```

**PostgreSQL:**

```sh
# No downtime needed — pg_dump is consistent
docker compose exec memos-db pg_dump -U memos memos > memos-dump-$(date +%Y%m%d).sql
```

<Notice type="info" title="Automate backups">
Set up a cron job to run these commands daily. For offsite safety, push the backup files to S3-compatible storage (MinIO, Backblaze B2, Bunny Storage). A missed backup is worse than a failed upgrade. At least with a backup you can roll back.
</Notice>

### Upgrade

Memos releases frequently. Always back up first and check the [changelog](https://usememos.com/changelog) for breaking changes.

```sh
# 1. Backup (see above)
# 2. Pull the new image
docker compose pull
# 3. Recreate containers
docker compose up -d
# 4. Verify
docker compose logs memos
# Look for: "Server started at http://localhost:5230"
```

<Notice type="warning" title="Upgrading from pre-v0.30.0?">
Memos v0.30.0 introduced private-mode-by-default. If your instance was publicly accessible before, set `MEMOS_INSTANCE_URL` to your public URL in the compose file **before** upgrading. Otherwise you'll lose anonymous access, RSS, and Explore after the restart. See the [changelog](https://usememos.com/changelog) for v0.28.0 SSO identity re-linking requirements as well.
</Notice>

After upgrading, clean up old Docker images to free disk space. See [How To Clean All Docker Images](https://www.bitdoze.com/cleanup-all-docker-things/).

## Troubleshooting

<Accordion label="Permission denied on volume (UID/GID mismatch)" group="troubleshooting">
Memos v0.30.0 runs as UID 10001 by default. If you see permission errors in the logs, the host volume directory may be owned by a different user.

Fix options:
1. Set environment variables to override: `MEMOS_UID=1000` and `MEMOS_GID=1000` (match your host user)
2. Or fix ownership: `chown -R 10001:10001 ./memos/`

See [How to Add Users to a Docker Container](https://www.bitdoze.com/add-users-to-docker-container/) for more on container user management.
</Accordion>

<Accordion label="Container won't start after upgrade" group="troubleshooting">
Check the logs for migration errors:

```sh
docker compose logs memos --tail 100
```

Common causes:
- Database migration failed. Restore from your pre-upgrade backup and check the changelog for breaking changes.
- `MEMOS_INSTANCE_URL` not set after upgrading to v0.30.0 (shouldn't prevent startup, but check)
- PostgreSQL not ready. Ensure the healthcheck passes (`docker compose ps` should show "healthy" for `memos-db`).

If you need to roll back: `docker compose down`, restore the backup, pin the image to a specific version tag (e.g., `neosmemo/memos:0.29.0`), and `docker compose up -d`.
</Accordion>

<Accordion label="Can't see public memos / RSS not working" group="troubleshooting">
Since v0.30.0, an empty `MEMOS_INSTANCE_URL` puts the instance in private mode. Anonymous visitors get redirected to sign-in, RSS feeds are unavailable, and Explore is hidden.

Fix: set `MEMOS_INSTANCE_URL` to your full public URL in the compose environment:

```yaml
environment:
  MEMOS_INSTANCE_URL: https://memos.yourdomain.com
```

Then restart: `docker compose up -d`
</Accordion>

<Accordion label="Database connection refused (PostgreSQL)" group="troubleshooting">
Make sure the `memos-db` container is healthy before Memos starts. The `depends_on: condition: service_healthy` directive handles this, but check:

```sh
docker compose ps
# memos-db should show "healthy" in the STATUS column
docker compose logs memos-db
# Look for: "database system is ready to accept connections"
```

Also verify that your `.env` credentials match between the Memos `MEMOS_DSN` and the PostgreSQL `POSTGRES_*` variables.
</Accordion>

<Accordion label="Memos is slow or unresponsive" group="troubleshooting">
Memos is lightweight. It runs on a Raspberry Pi with 1 GB RAM. If it's slow, the issue is almost certainly not Memos itself.

Check:
- Server resources: `docker stats memos` for CPU/memory usage
- Disk space: `df -h`. A full disk will cause write failures.
- Container resource limits: if you set `mem_limit` or `cpus` in compose, they may be too low
- Attachment storage: if you have many large attachments on a slow disk, uploads will lag

See [How To Monitor Server and Docker Resources](https://www.bitdoze.com/sever-monitoring/) for detailed monitoring setup.
</Accordion>

## Security and production hardening

Memos v0.30.0 runs as UID 10001 by default. Don't override with `user: root`, there's no reason to. Always put Memos behind a reverse proxy with TLS. Traefik + Let's Encrypt is the recommended path. Never expose port 5230 directly to the internet.

Only expose ports 80 and 443. Block direct access to 5230 from outside. For public instances, consider adding Traefik rate-limit middleware to prevent abuse.

Memos releases frequently. Subscribe to [GitHub releases](https://github.com/usememos/memos/releases) and update monthly. Set up automated daily backups to S3-compatible storage. A local backup on the same disk is better than nothing, but it won't save you from a disk failure.

<Notice type="info" title="Harden your server too">
Memos is only as secure as the server it runs on. Secure your VPS with [CrowdSec](https://www.bitdoze.com/crowdsec-secure-server/) and add [Traefik Basic Authentication](https://www.bitdoze.com/traefik-basic-authentication/) as an extra layer for private instances. Set up [Beszel & Uptime Kuma](https://www.bitdoze.com/beszel-uptime-kuma/) to get alerted if Memos goes down.
</Notice>

## What's new in Memos v0.27 to v0.30

If you installed Memos before August 2024, here's what changed:

| Version | Key features |
|---|---|
| **v0.27.0** | Voice notes, AI transcription (OpenAI/Gemini), MCP server at `/mcp`, SSE live refresh, `@username` mentions, filesystem attachment storage (new default) |
| **v0.28.0** | SSO identity re-linking (**breaking change**: existing SSO users must re-link after upgrade) |
| **v0.29.0** | Link preview cards, SMTP notification email settings, dedicated shortcuts page, configurable log level, instance statistics APIs |
| **v0.30.0** | Web Clipper (Chrome + Firefox), CodeMirror 6 editor with toolbar, multi-column layouts, Standard Webhooks with HMAC signing, deployment-managed config via `/etc/secrets` JSON, private mode default |

The Web Clipper and MCP server are the standout additions. The clipper lets you save web pages directly to Memos from your browser. The MCP endpoint makes Memos accessible to AI clients that support the Model Context Protocol.

## Conclusions

Setting up Memos with Docker Compose takes under 10 minutes. SQLite is the default for a reason: zero config, one volume, and you're done. PostgreSQL adds operational controls if you need them. Either way, back up your data and check the changelog before upgrading.

> If you are interested to see some free cool open source self hosted apps you can check [toolhunt.net self hosted section](https://toolhunt.net/sh/).

For more self-hosted tools and Docker setups, check out the [Best Self-Hosted Server Panels](https://www.bitdoze.com/best-self-hosted-panels/) or explore the full list below.

<Button text="Explore More Docker Containers" link="/docker-containers-home-server/" variant="solid" color="blue" size="md" icon="arrow-right" />