# Self-host your own services A series of 7 article(s), in reading order. # Self-hosted monitoring tool > Learn how to easily set up Uptime Kuma, a free, self-hosted website monitoring tool, using a simple Docker command. Keep track of your sites' uptime, ensure data persistence, and configure notifications. --- ![Self-hosted monitoring tool](/img/v2/docker_playing_with_app.webp) **TL;DR** This article shows how to self-host Uptime Kuma, a free website-monitoring dashboard, with a single `docker run` command (mounting a volume to persist monitored sites across restarts), then configure per-site checks and notifications (e.g. via Mattermost or email) when a site goes down. Imagine you're a web developer or you work in a web agency and you'd like to keep an eye on the sites you've developed for your clients, free of charge. Are these sites online now, or are they down? This work is carried out by site monitoring platforms. Could you install one on your computer? Of course! There are tons of *self-hosted monitoring tools*; one of them is [Uptime Kuma](https://github.com/louislam/uptime-kuma). You can play with the demo website here: . Just create an admin account (for free) and you're ready to add your first websites. As read in the [official documentation](https://github.com/louislam/uptime-kuma#-docker), you can easily start it using the command below. ```bash $ `docker run -d --restart=always -p 3001:3001 -v \${PWD}:/app/data --name uptime-kuma louislam/uptime-kuma:1` ``` > Screenshot — http\://localhost:3001 > > ![Dashboard](./images/dashboard.webp) > **Remember to add a volume:** > > The `-v ${PWD}:/app/data` flag (a [volume](/blog/docker-volume)) is important if you want to keep track of the sites you'll add to the dashboard (i.e. if you restart the tool). If you wish, only, play once with the interface first, you can omit it so nothing will be written on the disk. In the setting page, site by site, you can define a lot of actions like what the monitoring tool should do when the site is down. For instance, send you a notification on *Mattermost* or an email or ... ![Notification through Mattermost](./images/notification.webp) > **There are so many settings to configure:** > > On top of configuration items site by site, you'll also find in the *Profile -> Settings* page, a large number of global settings like the choice of the language's interface (French is available). Uptime Kuma watches your sites **from the outside**. For what happens on your own machine, [Get health information from your running containers](/blog/docker-healthy) does the same job for your containers. And once you self-host a few such tools, [Heimdall](/blog/heimdall-dashboard) is a nice way to gather them on a single homepage. --- # How to self-host Matomo > Self-host Matomo for free and gain GDPR-compliant website analytics. This simple guide covers installation, database setup, and Docusaurus integration. --- ![How to self-host Matomo](/img/v2/matomo.webp) **TL;DR** This article walks through self-hosting Matomo, a GDPR-compliant web analytics tool, on a subdomain: downloading and unzipping the release over SSH, creating a MySQL database, running the installation wizard, and finally wiring up the tracking script — including the `docusaurus-plugin-matomo` plugin for Docusaurus sites. [Matomo](https://matomo.org) is a GDPR-compliant tracking tool for your website. I've finally decided to install it so that I can get the blog's traffic figures; find out which articles are the most widely read, which topics are of most interest, etc., not just out of curiosity but also to get a better idea of the audience. I've chosen the [self-hosted solution](/blog/docker-memos) (because it's free) so that my traffic figures remain on my own server. The installation is a piece of cake: download a zip file, unzip and start the wizard. Really easy. There is a nice tutorial on . On my side, here are the actions I took: 1. Create a subdomain `matomo.avonture.be` pointing to a specific folder on my host. 2. Create an SSL certificate for the sub-domain. 3. Connect to that new folder using SSH * Run `wget https://builds.matomo.org/matomo.zip` to download the latest version * Run `unzip -o matomo.zip` to unzip and get files * Run `rm -f matomo.zip "How to install Matomo"` to remove two unneeded files Since I know I should have a MySQL database, I went to my dashboard. For [PlanetHoster](/blog/planethoster-n0c-spam), it's and there, I created a new MySQL db and its user. Finally, I just need to start the wizard on `https://matomo.avonture.be/index.php` and follow the guide. The wizard is really well done and very easy to follow. When done, I get my dashboard on and I just need to add my tracking script to my pages as explained in the wizard. ## Docusaurus On [this blog](/blog/docusaurus-docker-own-blog), I'm using [Docusaurus](https://docusaurus.io/) so I've installed a plugin for this: . --- # Install LimeSurvey using Docker > Learn to install LimeSurvey quickly and easily using Docker Compose. A complete guide on setup, data persistence with volumes, and running specific versions. --- ![Install LimeSurvey using Docker](/img/v2/limesurvey.webp) **TL;DR** This article shows how to spin up LimeSurvey, an open-source survey tool, using Docker Compose with the `martialblog/docker-limesurvey` image. It covers a minimal setup to get the site running at `http://localhost:8080`, adding volumes so your surveys and configuration survive container restarts, and pinning both LimeSurvey and MySQL to specific versions when you need to reproduce an older production environment locally. LimeSurvey is an open-source survey tool that allows users to create and conduct surveys online. It is a powerful and intuitive tool that can be used by everyone. Once again, it's easy to play with it and create a sandbox site to take a look at all its features; thanks to Docker. *Two things this `compose.yaml` relies on and that are explained elsewhere on this blog: [volumes](/blog/docker-volumes), so your surveys survive a container restart, and [healthchecks](/blog/docker-healthy), to make LimeSurvey wait until MySQL really accepts connections.* To do this, we'll use the Docker image. ## LimeSurvey, running in two minutes One `compose.yaml` file, one `docker compose up --detach`, and here is what waits for you on `http://localhost:`8080`/admin`: > Screenshot — http\://localhost:8080/admin > > ![LimeSurvey dashboard](./images/dashboard.webp) A full LimeSurvey installation — survey list, participants, statistics — running on your machine, with nothing installed except Docker. Let's build it. ## Let's play Please start a Linux shell and run `mkdir -p /tmp/limesurvey && cd $_` to create a folder called `limesurvey` in your Linux temporary folder and jump in it. Please then create a `compose.yaml` file in that folder with this content: **compose.yaml** ```yaml name: limesurvey services: limesurvey-app: image: docker.io/martialblog/limesurvey:latest container_name: limesurvey-app restart: always environment: - DB_TYPE=mysql - DB_HOST=limesurvey-db - DB_NAME=limesurvey - DB_USERNAME=admin - DB_PASSWORD=admin - ADMIN_USER=admin - ADMIN_PASSWORD=admin - ADMIN_NAME=admin - ADMIN_EMAIL=admin@example.com ports: - 8080:8080 depends_on: limesurvey-db: condition: service_healthy limesurvey-db: image: docker.io/mysql:8.4 container_name: limesurvey-db environment: - MYSQL_USER=admin - MYSQL_PASSWORD=admin - MYSQL_ROOT_PASSWORD=root - MYSQL_DATABASE=limesurvey healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] timeout: 10s retries: 10 ``` Now, simply run the following command to download (only the first time) required images (LimeSurvey and MySQL) and create the two containers: ```bash $ docker compose up --detach ``` Once everything has been downloaded and started, you can check you have two containers by running the following command: ```bash $ docker container list [...] IMAGE [...] NAMES [...] martialblog/limesurvey:latest [...] limesurvey-app [...] mysql:8.4 [...] limesurvey-db ``` > **The output above has been simplified:** > > For clarity, the output of `docker container list` has been simplified here above; not all columns were mentioned in the article. The `service_healthy` condition in the compose file ensures LimeSurvey won't start until MySQL is ready, which eliminates the connectivity errors you might have read about in older tutorials. Still, please wait **one or two minutes** (depending on your machine) for LimeSurvey to initialize its database tables. Go to `http://localhost:`8080 and if you see `ERR_CONNECTION_REFUSED`, wait a little longer and refresh. When it's ready, you should see the default LimeSurvey welcome page. > **Look at the logs:** > > If you have been waiting a long time and something seems wrong, run `docker compose logs -f` to inspect the logs. You should see `[core:notice] [pid 1] AH00094: Command line: 'apache2 -D FOREGROUND'` when LimeSurvey is ready, meaning the web server is up and accepting connections. > > Press `CTRL`-`C` to stop following the logs. ## First login When LimeSurvey is ready, you'll see the following page on `http://localhost:`8080: > Screenshot — http\://localhost:8080 > > ![LimeSurvey welcome page](./images/homepage.webp) Go to `http://localhost:`8080`/admin` to start the administration interface. The credentials to use are `admin` / `admin` (as defined in the `compose.yaml` file, see variables `ADMIN_USER` and `ADMIN_PASSWORD`). > Screenshot — http\://localhost:8080/admin > > ![LimeSurvey administration page](./images/admin.webp) Once logged in, you land on the dashboard shown at the beginning of this article, and you're ready to play with LimeSurvey on your machine. ## Using volumes The `compose.yaml` file provided above didn't use any volumes: when you stop running containers, your work will be lost and LimeSurvey will be restarted without any configuration / surveys just like a full reset. Perhaps, if you want to test LimeSurvey over several days and keep your configuration items, your surveys, then you will want to keep your work. To do this, you need to use volumes. Here is an updated `compose.yaml` file to ask Docker to use self-managed volumes. **compose.yaml** ```yaml name: limesurvey services: limesurvey-app: image: docker.io/martialblog/limesurvey:latest container_name: limesurvey-app restart: always environment: - DB_TYPE=mysql - DB_HOST=limesurvey-db - DB_NAME=limesurvey - DB_USERNAME=admin - DB_PASSWORD=admin - ADMIN_USER=admin - ADMIN_PASSWORD=admin - ADMIN_NAME=admin - ADMIN_EMAIL=admin@example.com # highlight-next-line volumes: # highlight-next-line - limesurvey:/var/www/html/upload/surveys ports: - 8080:8080 depends_on: # highlight-next-line limesurvey-db: # highlight-next-line condition: service_healthy limesurvey-db: image: docker.io/mysql:8.4 container_name: limesurvey-db # highlight-next-line volumes: # highlight-next-line - data:/var/lib/mysql environment: - MYSQL_USER=admin - MYSQL_PASSWORD=admin - MYSQL_ROOT_PASSWORD=root - MYSQL_DATABASE=limesurvey # highlight-next-line healthcheck: # highlight-next-line test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] # highlight-next-line timeout: 10s # highlight-next-line retries: 10 # highlight-next-line volumes: # highlight-next-line limesurvey: # highlight-next-line data: ``` > **Want to learn more about volumes?:** > > In that case, please read this blog post: [Using volumes with Docker, use cases](/blog/docker-volumes) ## Download an old version > **This section pins MySQL 5.7, which is end-of-life:** > > MySQL 5.7 reached end of life in October 2023 and no longer receives security patches. Pinning it is intentional below, because the whole point is to mirror an old production environment on your machine. **Never use `mysql:5.7` for anything other than local reproduction of a legacy setup.** You know what? It's really easy to download an old version. Let's imagine the following situation: you need to intervene on an old installation of LimeSurvey. For the example, let's say you need to install a plugin of some kind but, obviously, before doing so on a production site, you're going to get your hands dirty locally. So you need to download the same version as the production version. Earlier in this article, we saw that we can easily download the 'latest' version. Let's start again, but this time for a specific version. By looking at the production site, you see f.i. version `3.22.6+200219`. In our `compose.yaml`, we then need to replace `image: docker.io/martialblog/limesurvey:latest` by something else but what? A valid tag for sure. Go to and, in the `Filter Tags` area, type `3.22.6`, our production version thus. We'll get three images but just one for `apache` (indeed, we wish to use the Docker image having both PHP and Apache). Bingo, now we know our line will be `image: docker.io/martialblog/limesurvey:3.22.6_200219-apache`. The second part is how to be sure which lines I need to put in the yaml file. For this, just go to and try to find the same release. Here it's: . Click on the `Source code (zip)` for instance to download the archive and open it. From the archive, retrieve the `compose.yaml` file and pay attention to how the file is configured. For instance, we can see the version of MySQL used then was `mysql:5.7` (so, use the same to avoid conflicts). Our `compose.yaml` will then become: **compose.yaml** ```yaml name: limesurvey services: limesurvey-app: image: docker.io/martialblog/limesurvey:3.22.6_200219-apache container_name: limesurvey-app restart: always environment: - DB_TYPE=mysql - DB_HOST=limesurvey-db - DB_NAME=limesurvey - DB_USERNAME=limesurvey - DB_PASSWORD=secret - ADMIN_USER=admin - ADMIN_PASSWORD=admin - ADMIN_NAME=admin - ADMIN_EMAIL=admin@example.com ports: - 8080:80 depends_on: limesurvey-db: condition: service_healthy limesurvey-db: image: mysql:5.7 container_name: limesurvey-db environment: - MYSQL_USER=limesurvey - MYSQL_PASSWORD=secret - MYSQL_ROOT_PASSWORD=secret - MYSQL_DATABASE=limesurvey healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] timeout: 10s retries: 10 ``` Just run `docker compose up --detach` and surf to `http://localhost:`8080 and, congratulations, you have a local LimeSurvey v3.22.6 website. ## Conclusion Two services in a `compose.yaml`, a `service_healthy` condition so LimeSurvey waits for its database, and a couple of named volumes when you want your surveys to survive: that's all it takes to get a disposable — or a persistent — LimeSurvey on your own machine. And because the version is just a Docker tag, reproducing the exact release running in production is a one-line change. Same recipe, other tools: browse the [self-hosted](/blog/tags/self-hosted) articles of this blog. --- # Heimdall - Web dashboard > Tired of old browser bookmarks? Learn how to self-host and set up Heimdall, a beautiful web dashboard that gives you immediate, personalized access to all your favorite applications and links via Docker. --- ![Heimdall - Web dashboard](/img/v2/docker_playing_with_app.webp) **TL;DR** Tired of cluttered browser bookmarks? This article introduces Heimdall, a visually appealing, self-hostable web dashboard for organizing your favorite applications and links. Learn how to quickly deploy Heimdall using Docker with a simple `compose.yaml` file. The guide walks you through the initial setup, from customizing the appearance and creating organizational tags to adding your first applications. It also explains how to ensure your dashboard's configuration persists even if the container is removed. > I've never been a big fan of browser's bookmarks and in fact I have almost none. And, quite logically, I almost never display the favorite bar. I only visit a few sites on a regular basis and my browser history remembers them; otherwise a famous search engine remembers them for me. However, I'm not averse to a personalized homepage and I'm quite happy with Heimdall at the moment. Heimdall is a web application you can self-host (and it's a piece of cake using Docker) on your computer. Here is the official demo: ![Heimdall - Web dashboard](./images/heimdall.gif) You will define the look\&feel of your homepage by specifying which links you want to have immediate access to and, for others, you will classify them according to folders. Nothing too complicated. But it does give you a much nicer way of accessing your site than the outdated bookmarks management. It's perhaps one of the things that hasn't changed in thirty years. ## Creating your dashboard Please create a dummy folder and jump in it: ```bash $ mkdir ~/tools/dashboard && cd $_ ``` In that folder, please create a file called `compose.yaml` with the following content (refers to [official Docker Hub page](https://hub.docker.com/r/linuxserver/heimdall/) for the latest version): **compose.yaml** ```yaml # cspell:disable name: tools services: heimdall: image: lscr.io/linuxserver/heimdall:latest container_name: heimdall environment: - PUID=${USER_ID:-1000} - PGID=${GROUP_ID:-1000} - TZ=Europe/Brussels volumes: - ./config:/config ports: - 80:80 - 443:443 restart: unless-stopped ``` > **Note:** > > In the code here above, I've preferred the syntax `PUID=${USER_ID:-1000}` and `PGID=${GROUP_ID:-1000}` instead of just hardcoding `1000`: on my computer, my Linux user id is `1002`; not `1000` so I prefer to use a variable and, if the variable isn't defined, then use `1000` as default. Same for the group id. ## Running the dashboard Once the `compose.yaml` file has been created, just run this command: `USER_ID=$(id -u) GROUP_ID=$(id -g) docker compose up --build --detach`. This command will define our two `USER_ID` and `GROUP_ID` variables, initialise them to your own values then run the container. > **Note:** > > If you've hardcoded IDs in the `compose.yaml` file to, f.i. `1000` (because you know it's your IDs); then the command becomes shorter: `docker compose up --build --detach` ## First run Once you've fired the `docker compose up --build --detach` command, you've already created the container i.e. you can already surf to `http://localhost` to see it in action. > Screenshot — http\://localhost > > The default welcome page Let's play, click on the last button bottom right (the settings icon) and under `Appearance`, let's upload a new background image. > Screenshot — http\://localhost > > My own background It's a lot more fun, isn't it? ### Saving the configuration on disk Did you notice that, in the `compose.yaml` file there is a section called `volumes` with the following configuration? **compose.yaml** ```yaml volumes: - ./config:/config ``` It means that the `config` folder of Heimdall will be stored on your disk; in a folder called `config` too. *That's the [volume mechanism](/blog/docker-volume) at work.* I can check this by running `ls -alh` on my host: ```bash Permissions Size User Group Date Modified Name drwxr-xr-x - christophe christophe 2025-01-26 10:36 config .rw-r--r-- 284 christophe christophe 2025-01-26 10:36 compose.yaml ``` And, thus, everything I'll do in Heimdall will be saved on my computer. So, it means, if I've to stop and remove the Docker container, by running it again, I'll retrieve all my settings. ### Adding some links I suggest that, before adding new links, you first click on the Tags icon (the fifth icon bottom right) because you'll organize your links using tags and you have to create tags first. > Screenshot — http\://localhost > > Adding tags Add as many tags as you want. For this tutorial, I'll just create one. When finished, click on the `Cancel` button top right or click on the fourth button bottom right (called `Application list`) Create a new application. You can choose between an `Application type` or a `Website`. In my current use, I always choose `Website`. I fill then the URL and click on `Go`. Heimdall will extract the favicon in different sizes. I click on the one I wish. > Screenshot — http\://localhost > > Adding Docker Automatically, Heimdall has filled in some properties in the bottom part of the screen. I'll remove `Home dashboard` from the `Tags` area and, instead, type `Docker` i.e. the tag I've previously created. > Screenshot — http\://localhost > > Adding Docker Once saved, I'll get my new homepage: > Screenshot — http\://localhost > > Homepage with Docker We can see that tags are represented as a folder. If I click on it, I'll *enter* in the *folder* and see all applications having this tag. I can, too, add applications that will appear immediately on the homepage (like shortcuts on your desktop). To do this, create a new application and keep `Home dashboard` in the list of tags. Let's explore that `Application Type` feature: > Screenshot — http\://localhost > > Application type It's just something like a snippet. If I select `Bitwarden` from there, Heimdall will do basic initializations; it'll fill in the `Application name` textbox and the icon. Ok, why not. I'll point the URL to `https://vault.bitwarden.com/#/login` and I'll keep `Home dashboard` as tag. > Screenshot — http\://localhost > > With Bitwarden Here is the dashboard I'm using at work. Since it's so easy to add new items; I'm doing changes to it as soon as I'm working on a new project. > Screenshot — http\://localhost > > Final Heimdall dashboard ### More customizations By clicking on the last icon bottom right, you can customize a few more things in Heimdall. You can display f.i. a search engine which is nice. You can customize the CSS and add some JS too. ## Continue your journey Visit the official site to get updates and docs. Two other self-hosted applications I keep running next to Heimdall: [Uptime Kuma](/blog/docker_uptime_kuma), to know when one of those links is down, and [Memos](/blog/docker-memos) for note-taking. Get inspirations by searching **dashboard heimdall homepage** on [Google Search images](https://www.google.com/search?sca_esv=e645136bb336bc61\&q=dashboard+heimdall+homepage\&udm=2). --- # Excel Formula Beautifier > Make your complex Excel formulas easy to read! Use the Excel Formula Beautifier to instantly format and clarify your spreadsheet formulas for better understanding. --- ![Excel Formula Beautifier](/img/v2/excel.webp) **TL;DR** Struggling to read and debug long, complex Excel formulas? This article introduces the 'Excel Formula Beautifier,' a simple web tool that instantly transforms your convoluted formulas into a clean, indented, and easy-to-understand format. Just paste your formula, click a button, and see the logic clearly laid out, making it much easier to analyze and troubleshoot. Can you read `=IF(AND(A1<100,B1<>"",ISNUMBER(SEARCH("abc",C1))),TEXT(D1,"dd-mm-yyyy")&" - "&ROUND(E1/F1,2),"N/A")` without thinking, ok, let's take a few seconds to analyze the formula and understand what it does? On my own, no, I have to stop for a moment and take the time to read before I understand. Years ago, for fun, I've created . It joins the small family of self-hosted formatting tools I've built over time, like my [SQL Formatter](/blog/sql-formatter). Let's discover it. ## How to use A picture is worth a thousand words... > Screenshot — https\://excel-formatter.avonture.be/ > > Demo So simply copy/paste your long formula in the first text area and click on the Beautify button. > **Information:** > > The script will try to find if the used separator is `,` or `;` and, in case of trouble, just specify the good one. You'll get this type of result: ```none =IF( AND( A1 < 100; B1 <> ""; ISNUMBER( SEARCH( "abc"; C1 ) ) ); TEXT( D1; "dd-mm-yyyy" ) & " - " & ROUND( E1 / F1; 2 ); "N/A" ) ``` Which makes things clearer, no? If not, here this the transcript: If: * `A1` is less than `100`, * `B1` is not empty and * `C1` contains `abc`, Then: `D1` will be formatted as an european date and appends a rounded ratio of `E1`/`F1`. Else: Returns `N/A`. --- # karakeep - The Bookmark Everything App > Discover karakeep, the self-hosted bookmark, note-taking, and image manager. Follow this easy guide to install the "Bookmark Everything App" using Docker Compose. --- ![karakeep - The Bookmark Everything App](/img/v2/docker_playing_with_app.webp) **TL;DR** This article introduces karakeep, a self-hosted, all-in-one application for managing bookmarks, notes, and images, highlighting its powerful search capabilities. The guide provides a step-by-step tutorial on how to install karakeep using a custom Docker Compose setup. It explains how to configure the `compose.yaml` file to persist data on the host system for easy access and to set the correct user permissions. After installation, the post walks through the initial user setup and demonstrates key features like adding new items and organizing them into hierarchical lists. For a few months now, I've been using [Heimdall](/blog/heimdall-dashboard) but it has one major weakness: its hyper-basic search engine (which is even non-existent, since it's not possible to search for a link globally). So I was rather dissatisfied and waiting to find something better: karakeep. It's a tool that you can self-host and which is a bookmark, note-taking and even image manager that works very simply. *It joins the small set of self-hosted tools I run daily, next to [Memos](/blog/docker-memos) and [Uptime Kuma](/blog/docker_uptime_kuma) — all grouped in Docker Desktop thanks to the trick in [Docker - How to group containers together](/blog/docker-name-property).* And the search engine is quite powerful. Overall, karakeep is a pleasure to use. Let's see it in action. ## Add a Bookmark Once karakeep is running, adding something is really piece of cake: just click in the **New Item** text area and start to type something (it can be pure text like a reminder note or a website URL). I'll type `https://awesome-docker-compose.com/apps`, save it and immediately, I'll get this: > Screenshot — http\://localhost:2000/dashboard/bookmarks > > ![Awesome Docker](./images/awesome_docker.webp) As you can see, karakeep has grabbed a picture of the web page — no manual thumbnail, no extra step. ## Why It Works * One search box covers bookmarks, notes and images at once — the gap that sent me looking for a replacement in the first place. * Saving a link automatically captures a preview of the page, so a list of bookmarks stays visual, not a wall of bare URLs. * Notes, images and links live in the same hierarchical lists, so you're not juggling three separate self-hosted apps for three related jobs. * It's self-hosted, so your bookmarks and notes stay on your own disk, next to the other tools in [Heimdall](/blog/heimdall-dashboard). ## Let's Install karakeep Please create a folder like `mkdir ~/tools/karakeep && cd $_`. In that folder, please create a `compose.yaml` file with the content below: **compose.yaml** ```yaml # cspell:disable name: tools services: web: image: ghcr.io/karakeep-app/karakeep:${KARAKEEP_VERSION:-release} restart: unless-stopped volumes: - ./data:/data ports: - 2000:3000 env_file: - .env user: ${UID:-1000}:${GID:-1000} environment: MEILI_ADDR: http://meilisearch:7700 BROWSER_WEB_URL: http://chrome:9222 DATA_DIR: /data chrome: image: gcr.io/zenika-hub/alpine-chrome:123 restart: unless-stopped command: - --no-sandbox - --disable-gpu - --disable-dev-shm-usage - --remote-debugging-address=0.0.0.0 - --remote-debugging-port=9222 - --hide-scrollbars meilisearch: image: getmeili/meilisearch:v1.13.3 restart: unless-stopped env_file: - .env environment: MEILI_NO_ANALYTICS: "true" volumes: - meilisearch:/meili_data volumes: meilisearch: ``` > **Note:** > > The line `user: ${UID:-1000}:${GID:-1000}` will ask Docker to use a specific user (not the `root` one) while creating files on your disk. > > The user `1000:1000` is, in most cases, you i.e. it's your current Linux user (type `id -u` and, then, `id -g` to retrieve your user ID and your group ID and you'll see it'll be `1000` for both). > > If your IDs are not `1000`, please edit the yaml file and put your IDs instead. > **Information:** > > Compared with the default Docker configuration, I've made one change, which is to store the data on the hard disk rather than using a Docker volume. > > I had to reinstall Karakeep following a configuration change and I had to export the volume to retrieve the data (Docker Desktop offers this feature). > > To avoid this situation, I might as well keep the files immediately on my hard disk. Once the `compose.yaml` file has been created, in your console, now, just run `docker compose up --build --detach`. You now have karakeep running. Surf to `http://localhost:`2000`/` and you'll get this screen: > Screenshot — http\://localhost:2000/ > > ![karakeep - Logon screen](./images/logon.webp) Because it's your first time, click on `Sign Up` and let's create a new user. karakeep will force the password to respect some rules so, once defined, don't forget to add it in your password safe program (Bitwarden for my own). You'll get the dashboard: > Screenshot — http\://localhost:2000/dashboard/bookmarks > > ![karakeep - Dashboard](./images/dashboard.webp) ## Adding to a list First, create a list and fill in the different options (name, icon, ...): ![Creating a list](./images/adding_list_1.webp) Being back to the dashboard, click on the `...` button of the desired item and click on the `Manage Lists` option to be able to create a new list: ![Adding to a specific list](./images/adding_list_2.webp) It's also possible to create a list as a child of an existing one and thus, you can easily do something like this: ![Examples of lists](./images/lists.webp) ### Tips - Select a list before adding an item By first selecting a list, new items will be added directly to that list. ## Extra Features ### Import / Export By clicking on your profile avatar (top right) then on `User Settings` you will be able, among other things, to access an `Import / Export` feature. It's good to know; you can import from a lot of sources and also export as a JSON or an HTML page. ## Conclusion karakeep replaced Heimdall's non-existent search with one box that covers bookmarks, notes and images, and it does the one thing a plain bookmark manager doesn't: it grabs a visual preview of every link you save. A `compose.yaml`, a couple of UID/GID lines, and it runs next to [Memos](/blog/docker-memos) and [Uptime Kuma](/blog/docker_uptime_kuma) in the same self-hosted stack. Refer to the official site for more info, and to [Docker - How to group containers together](/blog/docker-name-property) to keep it tidy alongside your other self-hosted tools. --- # Self-hosted note-taking application > Deploy the memos self-hosted note-taking application quickly using Docker. Learn how to use its simple features - Markdown, checklists, tags, and image pasting. --- ![Self-hosted note-taking application](/img/v2/docker_playing_with_app.webp) **TL;DR** This article introduces 'memos', a lightweight and simple self-hosted note-taking application perfect for developers' daily reminders and tasks. The guide provides a complete walkthrough of installing memos using a custom Docker Compose setup, emphasizing how to configure it for data persistence and correct file permissions. You'll learn how to get started, create notes using Markdown and checklists, organize them with tags, and use advanced filters called 'shortcuts'. The post also highlights practical features like the ability to paste images directly into your notes. As a developer, I'm working all day on a computer and sometimes (too often in fact), at the end of the day, I send myself an email for tomorrow's me *Don't forget to ...*, *you have to continue \[a task]*, *I stopped at \[a file], I have to continue the others*, in short, I use my inbox like a to-dos/to-remember manager. Can we do better? Of course, but which tool should I choose? What I'd like: to be able to write notes very easily, for some notes to be tasks (a checkbox is enough for me) and for me to be able to copy/paste images such as a screenshot. Ideally, I'd like to have a local website that I can consult every day as a reminder. Simplicity is the key. There are a lot of applications but, because I really need one simple and fast (it's the end of the day, I should leave for my train, hop hop, a few seconds, ok, the note for tomorrow has been created), I've tried **[memos](https://www.usememos.com/)** and I appreciate it a lot. ![memos demo](./images/memos.webp) ## Let's install memos This time, I will not suggest to use the temporary folder since we'll need to keep track of our notes, pasted images, ... on our disk during the next couple of days, weeks or months. *This is done with a [Docker volume](/blog/docker-volume): the container can be destroyed and recreated, your notes stay on your disk.* So, please run `mkdir -p ~/tools/memos && cd $_` to create a folder in your home directory and jump in it. Then, please create a file called `compose.yaml` with this content: **compose.yaml** ```yaml name: tools services: memos: image: neosmemo/memos:stable container_name: memos init: true user: ${UID:-1000}:${GID:-1000} ports: - "5230:5230" volumes: - ./data:/var/opt/memos restart: unless-stopped ``` > **Note:** > > The line `user: ${UID:-1000}:${GID:-1000}` will ask Docker to use a specific user (not the `root` one) while creating files on your disk. > > The user `1000:1000` is, in most cases, you i.e. it's your current Linux user (type `id -u` and, then, `id -g` to retrieve your user ID and your group ID and you'll see it'll be `1000` for both). > > If your IDs are not `1000`, please edit the yaml file and put your IDs instead. In your console, now, just run `docker compose up --build --detach`. In just six seconds, memos has been installed on my machine and a container has been created: ![Installing memos](./images/installation.webp) Go to `http://localhost:`5230 and you'll see the website is already running: > Screenshot — http\://localhost:5230 > > ![First run](./images/first_run.webp) > **Information:** > > See on the bottom left; it's possible to select another language; French is supported f.i. I'll create my account (`admin/admin` since I'm working on my localhost) and I'm ready to create my first note. It took me less than one minute until now. > Screenshot — http\://localhost:5230 > > ![The memos homepage](./images/homepage.webp) Click in the `Any thoughts...` edit box and start to type your thought... For instance: ```markdown **Python Codebase - User Authentication Module:** - [ ] Implement password hashing using bcrypt. - [ ] Create user registration endpoint (`/register`). - [ ] Develop user login endpoint (`/login`) with JWT token generation. ``` > Screenshot — http\://localhost:5230 > > ![My first note](./images/first_note.webp) By pressing the `Save` button, the note is created. It's clean and, tomorrow, oh nice, oh yes, that's true, I need to do these three things. Imagine it's tomorrow and I've achieved the first one, I just need to check the box and it's automatically saved: > Screenshot — http\://localhost:5230 > > ![The first task is done](./images/first_task_is_done.webp) I can still edit, pin, delete, archive, ... the task using the three-dots button: ![The different options](./images/task_options.webp) ## Let's create a few more tasks As you can see, even it's optional, let's assign one or more tags to a note. Simply by using the hash syntax: ```markdown **Python Codebase - User Authentication Module:** #python - [x] Implement password hashing using bcrypt. - [ ] Create user registration endpoint (`/register`). - [ ] Develop user login endpoint (`/login`) with JWT token generation. ``` ```markdown Investigate and resolve the bug in the budgetary application that occurs when initializing a new financial exercise. #budget ``` ```markdown Write the missing documentation for the new user authentication module, covering API endpoints, data models, and setup instructions. #python ``` ```markdown Review the Dockerfile for the user authentication service to ensure optimal image size and security practices. #docker #python ``` > Screenshot — http\://localhost:5230 > > ![A few more tasks](./images/few_tasks.webp) ## Filtering using tags When you've used tags, you'll see them immediately bottom left under `Tags`. Simply click on a tag to filter on it. You can click on more than one if you want. In the example below, I've three notes for `Python` and just one with the Docker tag: > Screenshot — http\://localhost:5230/?filter=tagSearch%3Apython%2CtagSearch%3Adocker > > ![Filtering using tags](./images/filtering_using_tags.webp) ## Creating shortcuts Next to tags, you can use more sophisticated filters like what memos call `shortcut`. ![Creating shortcuts](./images/creating_shortcuts.webp) The used filter is `has_task_list && tag in ["python"]`. Now, just click on the filter to enable it. You'll just see one task (because the other tasks with the `python` tag didn't have a checkbox). Really easy no? [Learn more about writing filters](https://usememos.com/docs/usage/shortcuts) ## Pasting images Just like you do in any modern application, copy the image in the clipboard and paste it in the text area. Add if you want some text. ## Customization By clicking on the bottom left profile icon, you can update the application's settings like setting `Monday` as the first day of the week (for the top left calendar). You can also inject some CSS / JavaScript to customize the look\&feel of memos. ## Conclusions As introduced in the preamble, personally, it's more than enough for my needs. I wanted something ultra-simple (a tab in my browser) and the ability to write my notes very quickly (with Markdown support), the possibility of having checkboxes and being able to copy/paste a screenshot. Memos go a step further with tag and shortcut management, and the ability to restrict the to-do list to a calendar period. And it's all 100% free! ## More info Please refer to the official site to get more info. *Two other self-hosted tools running next to memos on my machine: [Heimdall](/blog/heimdall-dashboard), the dashboard from which I open all of them, and [Uptime Kuma](/blog/docker_uptime_kuma) for monitoring.* ---