Recently I published the source code for my project, Chimera-md. I think it’s now ready to unveil to more folks. A key challenge there was making it easier to install. Now there’s a Docker image for running on 64-bit Linux servers. (Potentially other platforms would be valuable? But I think this is the current expectation.)
A couple months ago I didn’t know how to install a project like this on my NAS. If you have similar NAS hardware, maybe these instructions will be a help.
Installing on Synology NAS
In the Synology DSM, if you haven’t already, you’ll need to install Container Manager from the Package Center. Container Manager is Synology’s graphical front-end for Docker. It makes it fairly easy to install and manage Docker images and containers. I say “fairly” because it’s Linux, and nothing in Linux is ever actually easy.
It took me a long time to wrap my head around how Docker works. It presents a bunch of different views, and some of them definitely overlap in functionality. Here’s what you need to know. In Docker, an Image is like an installer; it contains everything you need to run whatever is in there, but it needs to be applied to your system first. A Container is the concrete “installed” version. It doesn’t expand the files like an installation in Windows would, though. It’s just a configuration wrapper around the image with the needed local resources.
Often said resources will be one or more ports, if it’s a server application, and usually some number of local paths. Chimera-md, for example, is a web server. It listens on an HTTP port (8080), and serves files from some “document root” — a folder you designate as the top of the folder hierarchy. The Image doesn’t know what these should be for your system. But the magic of Docker is that it doesn’t need to know. You tell it how to do that stuff in the Container’s configuration, and Docker maps those things in.
A description of what these resources are for a given project will be described in a Docker compose file, usually called compose.yaml. I’ve had a lot of success when going to install something just by searching for “<project-name> docker compose”.
Let’s look at one as an example. This is the current (as of this post) version of the Chimera-md docker compose file. And here is a link to the actual current one.
version: '3.8'
services:
chimera-md:
container_name: chimera-md
platform: linux/amd64
image: acbarrentine/chimera-md:latest
ports:
- "8080:8080"
volumes:
# /data/www is the web root -- Point this to your main documents folder
- /usr/data/user1/documents:/data/www:ro
# /data/search is where the full text indexer writes its work files
# These are persisted to optimize server startup times
# (Note: not read-only!)
- /docker/chimera-md/search:/data/search
# You may want to map an images folder
- /usr/data/user1/media:/data/www/images:ro
# You can map in a favicon, if you'd like
# Or just put a file called favicon.ico in your document root
# - /usr/data/user1/images/logo.png:/data/www/favicon.ico:ro
# Optional extra document directories can be added to the web root
# - /usr/data/user1/notes:/data/www/notes:ro
# Similarly, you can map additional image directories
# - /usr/data/user1/icons:/data/www/images/icons:ro
environment:
# Chimera provides a number of environment variable-based configuration options. Only a few
# are relevant to Docker installations
# Site title appears in the <title> tags of served pages
- CHIMERA_SITE_TITLE=Chimera-md
# What is the name of the index file to serve for a folder URL?
- CHIMERA_INDEX_FILE=index.md
# Tracing log level. In descending verbosity, options are TRACE, DEBUG, INFO, WARN, ERROR
# Case matters
- CHIMERA_LOG_LEVEL=INFO
# If a directory doesn't contain an index.md file, should we generate one?
- CHIMERA_GENERATE_INDEX=true
restart: unless-stopped
I’ve put more comments in this thing to help navigate it than you might typically see, but if you’re not familiar with these compose files, parts of it still bear description.
A given Docker image may comprise any number of “services”. A complicated project like Immich has several images — processes that get spun up and cooperate to provide the program functionality. Chimera-md is comparatively simple, just having one called “chimera-md”. By convention, all Docker images have lower-case names.
The indented block beneath the service is configuration for that service. First up is the image name. This tells Docker where to find the image and what version to download. Docker is a company, and they run an indexing service that can discover these things. Chimera-md is listed on Docker Hub. There’s a bunch of other package registries out there, but the public ones all feed into Docker Hub.
image: acbarrentine/chimera-md:latest
The name of my image is acbarrentine/chimera-md
. Image releases get tagged with a version number and by convention one is also tagged as “latest”. “Latest” may not actually mean the latest, and often doesn’t. So often you’ll see people say you should control for that by using one of the more concrete tags. The repository page lists all the available options. 0.1.6
is the latest as of this post, so perhaps you might change the image
line to refer to it.
image: acbarrentine/chimera-md:0.1.6
Next up are ports — the indented dash thing says it’s an array. In this case, an array of length one. A service can have multiple ports, though, so this is how you do it.
ports:
- "8080:8080"
Chimera-md is a web server, which traditionally listens on both 80 (http) and 443 (https), but for my own reasons, I have chosen not to support https and to use port 8080. (It’s so I don’t have to use sudo
while developing the program.) What it wants to listen on, though, is irrelevant to you, because you have Docker, and you can tell it to use any port you want. That’s why there are two numbers in the mapping. The left side of the colon is your port, the right side is what the image is going to use. You can change the left, don’t change the right. So if you wanted to use the default web port of 80, you’d say:
ports:
- "80:8080"
Personally, I use port 19840, but that’s because I use a combination of a reverse proxy and DNS server to hide that abstraction, and it just needs to be something that isn’t used by other services.
Now we come to the meat of things. Volume mappings are the heart of many a Docker container. When I was first reading about these things, I spent a lot of time confused. I am going to lay things out as plainly as I can, but there’s a decent chance you’ll be confused too. I’m here to let you know: don’t worry about that. It’s very easy to clean up behind a container and try it again if you did it wrong. It’s part of the magic of Docker. I might even recommend screwing up a few times just to get a feel for the relationship between images and containers.
But we’ll get back to that. Right now we’re talking about volumes. Volumes are how Docker lets you manage a container’s access to the file system. An image has an internal idea of a file system, places where it thinks files are at. In almost all cases, it is wrong. When a service is running in Docker, Docker itself is intercepting file-related system calls and redirecting things. A “file” might be located in the image itself (the executable sure is!), and Docker still needs to be able to serve that to it as if it were a regular file handle.
In addition to that, the container wraps the image with paths you provide it via volume mappings and serves those up in the exact same way as the internal image paths. You can even map files on top of the internal things, if you want. It lets you point at your real files with paths the image will understand. My web server wouldn’t be very useful to you if it could only serve files I built into the image!
On the right side of the colons are file paths the way the image expects them; on the left are your paths. Let’s get concrete and look at some.
volumes:
# /data/www is the web root -- Point this to your main documents folder
- /usr/data/user1/documents:/data/www:ro
#
starts a comment. That’s me trying to describe how to use things in the Docker file.-
starts an actual mapping,/usr/data/user1/documents
to/data/www
.:ro
is Docker compose parlance meaning “read-only”; the container will not be able to write to this path. So what does it all mean?
The part on the right, /data/www
, is fixed in the program. That is the web root for this image. There’s no changing that. It’s the root of the tree of files the web server can see. It’s probably not where your files are, but that doesn’t matter. Docker is going to map something to it. That’s the thing on the left side. /usr/data/user1/documents
— that’s probably wrong too. That’s me just guessing about what your organization might be. Where are your documents? I keep mine in the /home
shared folder on my Synology NAS. The actual path for that, which you can see from the Properties page in DSM’s File Station is /volume1/homes/acbarrentine/Documents
.
So that’s what we’ll point Docker to. We’ll rewrite that mapping to point at my actual Documents folder.
volumes:
- /volume1/homes/acbarrentine/Documents:/data/www:ro
Right? When the image goes looking for /data/
www, Docker will actually serve up files from /volume1/homes/acbarrentine/Documents
.
We have mapped local resources into the Docker image. We made a container! Well, we’re getting there. There are more mappings to provide. And environment variables. But we’re getting there! Let’s look at the next one.
# /data/search is where the full text indexer writes its work files
# These are persisted to optimize server startup times
- /docker/chimera-md/search:/data/search
Persistent data! Chimera-md is going to write some stuff to this directory. Note the lack of a :ro
on the end of it. It’s also going to be noise data, not something you’d mistake for a readable document. We need to give this stuff a place to live, but it doesn’t need to be your Documents folder. It should be someplace nice and forgettable. The traditional thing to do is to give Docker containers their own little folder to do with as they please. Why can’t temp files get written into the image itself? Because it is set in stone when you downloaded it. And a container can’t contain files itself; it can only map yours into the image.
On Synology, each Docker image gets its own little folder in the /docker
shared folder.
You’re going to want to maintain that pattern. Each container gets an all-lower-case folder beneath /docker
, in which Container Manager will deposit that container’s compose.yaml file. You might have additional files or folders in there to support the container. In this case, I have the search
folder and a site.css
style file. Paths to these are lightly abstracted by Synology’s File Station, but the real paths can be seen by looking at their Properties. Here are my Docker mappings for them.
- /volume1/docker/chimera-md/search:/data/search
- /volume1/docker/chimera-md/site.css:/data/www/style/site.css:ro
An important thing just happened there. I kind of jumped forward a little and threw in an extra concept. Did you see it? That site.css
mapping is for a file, not a folder. What’s more, I mapped it inside another folder’s mapping. To the image, it lives inside the web root. I could have put it in a style
folder in my real Documents
folder, but it’s not really a document, is it? It exists only for chimera-md purposes. It made more sense to me to put it in the container’s work folder and use a Docker volume mapping to put it where the image expects to find it.
Did that make sense? That right there is the big concept with Docker volumes. If you’ve got that, the rest of this is going to be a cake walk. Don’t worry if it didn’t take, though. Just tag it in your brain to come back to later. Like I said, you can scrap the container and try it again later. But I’ll say this here because it confused me for a long time. That folder is not the container. It only has files that support the container. You do not need to delete the folder to delete the container.
There’s another important concept that’s unfortunately going to be a little hard to contend with. It’s that volume mappings are largely optional. Mapping something to the web root for a web server is important! But strictly speaking, it is optional. The program will work without that. There’s even a couple files in /data/www
in the image. It’s one of those “Congratulations! You installed it correctly!” messages that’s so irritating to read. But it will work without a mapping. I say all this because the next line in the file is totally optional.
# You may want to map an images folder
- /usr/data/user1/media:/data/www/images:ro
Maybe you have images you want to serve. They spruce up a web page! Maybe your images live alongside your documents. Mine do not. Maybe you’re ready to add some images into the web pages you’re going to serve up. If not, comment the mapping out; you can always come back to it later. If you’re ready, dig up the path to your images folder. Or maybe, like me, you have a bunch of images folders you might want to serve in web pages. Maybe you need multiple mappings. Optional includes the choice to make multiple of them!
- /volume1/homes/acbarrentine/Paintings:/data/www/media/Paintings:ro
- /volume1/homes/acbarrentine/Photos:/data/www/media/Photos:ro
- /volume1/homes/acbarrentine/Paintings/Website/Logo.png:/data/www/favicon.ico:ro
Three of them! All mapped to different points under the web root. All read-only. We don’t want chimera-md writing anything in them. But now web clients can get files under /media/Paintings/
and /media/Photos
as well as my favicon.
That’s all the volume mappings. If you made it this far, take a drink. You’re doing great!
Next step, environment variables. These give you a way to configure settings that aren’t ports or files. Fortunately, they’re all pretty straightforward. Here’s the template for them:
environment:
# Chimera provides a number of environment variable-based configuration options. Only a few
# are relevant to Docker installations. If you would like, these can be offloaded into a file
# named .env, placed next to this compose.yaml file
# Site title appears in the <title> tags of served pages
- CHIMERA_SITE_TITLE=Chimera-md
# What is the name of the index file to serve for a folder URL?
- CHIMERA_INDEX_FILE=index.md
# What code block highlight style should we use?
# Syntax highlighting provided by highlight.js
# Styles available listed at: https://github.com/highlightjs/highlight.js/tree/main/src/styles
# Default is "a11y-dark", a dark theme
- CHIMERA_HIGHLIGHT_STYLE=a11y-dark
# Tracing log level. In descending verbosity, options are TRACE, DEBUG, INFO, WARN, ERROR
# Case matters
- CHIMERA_LOG_LEVEL=INFO
# If a directory doesn't contain an index.md file, should we generate one?
- CHIMERA_GENERATE_INDEX=true
These are not mappings. When you want to change value, you change the right side. Here are the values from my local compose.yaml file:
environment:
- CHIMERA_SITE_TITLE=Chimera
- CHIMERA_INDEX_FILE=index.md
- CHIMERA_LOG_LEVEL=INFO
- CHIMERA_GENERATE_INDEX=true
There’s one last little blurb. It covers restart behavior. If a service crashes, Docker can optionally start it back up. Hopefully my service doesn’t crash, but it’s young and I’m still finding bugs. There’s a variety of options, but I’ve mostly settled on restart: unless-stopped
. If I kill the service, don’t start it up again automatically.
That’s all of it. In the interest of simplicity, let’s look at it all put together.
version: '3.8'
services:
chimera-md:
container_name: chimera-md
platform: linux/amd64
image: acbarrentine/chimera-md:0.1.6
ports:
- "19840:8080"
volumes:
- /volume1/homes/acbarrentine/Documents:/data/www:ro
- /volume1/docker/chimera-md/search:/data/search
- /volume1/homes/acbarrentine/Paintings:/data/www/media/Paintings:ro
- /volume1/homes/acbarrentine/Photos:/data/www/media/Photos:ro
- /volume1/homes/acbarrentine/Paintings/Website/Logo.png:/data/www/favicon.ico:ro
- /volume1/docker/chimera-md/site.css:/data/www/style/site.css:ro
environment:
- CHIMERA_SITE_TITLE=Chimera
- CHIMERA_INDEX_FILE=index.md
- CHIMERA_LOG_LEVEL=INFO
- CHIMERA_GENERATE_INDEX=true
restart: unless-stopped
With that, we’re ready to make a container. Let’s turn our attention back to Container Manager in DSM. You’ve got a bunch of tabs to choose from. By now, you’ll recognize the names Image and Container. An Image is the kernel you’ll wrap a Container around.
I have 9 images. Do you recognize the tag on acbarrentine/chimera-md
? 0.1.6, that’s the version specified in my compose.yaml
. These images got downloaded automatically by Docker when it processed my compose.yaml
files. Each image
directive in them instructs it to download one of these.
Here are my containers. 11 of these. These are, again, being automatically created by Docker when it processes the compose.yaml
files. Remember when I said that file can describe multiple services? You’ll get one container per service. Chimera-md makes just one, but one of the programs I run makes four!
At times you’ll have to go into these tabs to manage containers or images. Mostly to delete them. But they’re not where your focus will be. Let’s turn our attention to the Project tab.
“Project” is what Synology is calling a compose.yaml
file. This is a one-to-one mapping. See “immich” there? That’s the one making 4 containers when it runs. Chimera-md is here too. Let’s walk through how to get it there. See that “Create” button? Go ahead and hit it.
We already know everything we need to make this work. But let’s bring it together.
- Project name: Name it whatever you’d like, but it must be all lower-case. Something related to the program you’re installing would make sense. I called mine
chimera-md
- Path: Remember I said there’s a folder under
/docker
for each program’scompose.yaml
? Here’s where you tell it where that will go. Make your folder under/docker
and name it the same thing as the project:/docker/chimera-md
- Source: If you’ve been following along, you probably have a compose file already, either in memory in some Notepad-like app, or saved out to the file system. Here’s where you hand it to Docker. You can save it and upload it, or edit it directly into a tiny text editor on this dialog (“Create docker-compose.yml”, they call it, breaking convention). If you already saved it into your project folder, it will even auto-discover it. One way or another, when you hit next, it will be copied into that folder
Hit “Next” when you’re ready to move on. It will ask you about making a web portal mapping for it. This is probably useful, but I never tried it because I’ve got that DNS/reverse proxy thing going. If you’re able to make use of it, let me know. I’d love to hear about it. I tell it to skip that.
We get a little summary thing — last chance to back out! — and then we’re off to the races.
Then a special Docker terminal window pops up and it pulls the image down from whatever repository listed it, wraps it with your compose.yaml
mappings, and starts running the services.
If it succeeds, you’ll get that “Exit Code: 0” message at the end. That’s Unix-speak for “No errors”. If there’s a problem, you’ll get some log information that will, hopefully, point you at the trouble. It’s likely to be something in the compose.yaml
mapping. Don’t feel bad if you do. I still get them regularly. Right now I’m fighting with one that requires me to make a fake, empty folder in my Documents folder. There’s a certain opacity to what’s going on inside a Docker container that can make troubleshooting a gamble.
Now you either have a working, running service, or one that requires some troubleshooting. You may get some good log information from the Container tab, which tracks output from the program. I don’t know how to guess at what those might be, but if you email me, maybe I can help.
What I can say is that sometimes you’re going to want to start over. This isn’t working! I just want to try again! Let’s cover how to do that without causing damage.
Remember what I said earlier: “You do not have to delete the /docker/chimera-md
folder”. That is neither the container, nor the image. It’s not even the “project”. It’s a place to put persistent support files for it.
When you want to start over, “stop the service” from the Projects page. Delete the containers. If you want, delete the images. (Generally speaking, it’s not needed to delete the images, but sometimes you just want to scorch the earth.) Then go back to Projects page, tweak your compose.yaml
and try to Build again. Docker will wrap it back up with your local resources, make a new container, and try again. This time, you’re probably going to be closer to the answer.
When it does work, you should be able to dial it up in your web browser. Go to <server-ip>:<your-port> in your web browser and see if it’s working!
192.168.0.107 is the IP address of my NAS. (There are lots of ways to find your IP address, but probably the easiest is the System Health widget on the DSM dashboard.) 19840 is the port I told it to use. Everything after that on the URL is a result of forwards from within the application itself.
What do you think? It’s not exactly the App Store. It’s got the stink of Linux all over it! But like the rest of Linux, it’s about giving you the power. When you’re ready to embrace it, it will be ready for you.
If you get it going and find some value in Chimera-md, let me know! I’d love to hear your story or your ideas about how I could do it better. I’ve had a grand time making this project. I’m super excited to debut it for the world and grow it into a service other people love as well!