Run it in Docker
A container runs the site the same way on any machine that has Docker. This page builds an image from the download and runs it alone. It is for whoever manages the server. If you want a database or object storage as well, go on to the Compose pages afterwards.
What you need to know first
- No image is published and the download has no Dockerfile. You build your own, from the unpacked download. The Dockerfile below is short, because the download is already built and has its dependencies installed. It was built and run for this guide.
- The container runs the launcher (
node server/launcher.js), so one-click updates work. The updates are kept in the data volume. - It uses the
node:22-alpineimage, runs as the unprivileged usernode, listens on port 3001, and keeps its data in/app/data. - Image and volume names use
choir-manager, the product's older name.
1. Unpack and add two files
mkdir choir-docker
tar -xzf choirmaster-cms-1.6.12.tar.gz -C choir-docker
cd choir-docker
Create Dockerfile in that folder:
# Choir Master CMS: one container built from the unpacked download.
FROM node:22-alpine
ENV NODE_ENV=production
WORKDIR /app
COPY . .
RUN mkdir -p /app/data && chown -R node:node /app
USER node
VOLUME ["/app/data"]
EXPOSE 3001
HEALTHCHECK CMD wget -qO- http://localhost:3001/api/health || exit 1
CMD ["node", "server/launcher.js"]
And .dockerignore beside it:
.env
.env.*
data
secrets
deploy
Dockerfile
.dockerignore
docker-compose*.yml
Do not skip .dockerignore. The Dockerfile copies the whole folder into the image, and without it your .env file, with its passwords and licence key, would be baked into the image. The same goes for a secrets folder, if you use Docker secrets later (see Docker: HTTPS, ports and volumes).
| Line | What it does |
|---|---|
FROM node:22-alpine | A small image with Node 22. |
ENV NODE_ENV=production | Production mode: Secure cookies and SITE_URL required. It is also the server's default. |
COPY . . | Puts the unpacked download in /app. There is nothing to build or install. |
chown -R node:node /app | Lets the unprivileged user write, so it can make the database and keep downloaded updates. |
VOLUME ["/app/data"] | Marks where the data lives, so Docker keeps it apart from the container. Always mount a named volume there (below). |
HEALTHCHECK | Docker asks /api/health every 30 seconds and shows the container as healthy. |
CMD | Starts the launcher. |
2. Write the settings file
Create .env in the same folder:
SITE_URL=https://choir.example.org
ADMIN_USERNAME=admin
ADMIN_PASSWORD=change-me-to-something-long-and-random
LICENSE_KEY=cmk_your_key_here
EMAIL_PROVIDER=log
EMAIL_FROM=Harmony Community Choir <noreply@example.org>
For docker run --env-file, write values without quotes. Unlike most settings files, Docker keeps the quotes as part of the value, so EMAIL_FROM="Harmony ..." would send mail from an address that starts with a quote mark. Compose removes the quotes, so there either way works.
This is the small version. What each setting does, and the full list, are in The settings every site needs. Replace log with a real email service before the choir uses the site: Email: what the site sends and how to choose a provider.
The container's defaults are SQLite at ./data/choir.sqlite and local files at ./data/storage, both inside the volume. Do not set SQLITE_PATH or STORAGE_LOCAL_DIR unless you want them elsewhere.
3. Build and run
docker build -t choir-manager .
docker run -d --name choir-manager \
--restart unless-stopped \
-p 8080:3001 \
--env-file .env \
-v choir-data:/app/data \
choir-manager
| Part | Meaning |
|---|---|
-p 8080:3001 | Publish the container's port 3001 on the host's port 8080. Change 8080 to change the host port. |
--env-file .env | Pass the settings in. |
-v choir-data:/app/data | A named volume, choir-data, that holds the database, uploads, downloaded releases and pre-update copies. It survives removing and recreating the container. |
--restart unless-stopped | Start it again after a crash or a reboot of the host. |
4. Check it
docker ps
curl http://localhost:8080/api/health
docker logs choir-manager
After about 15 seconds docker ps shows (healthy). The health answer is {"status":"ok","platform":"node","version":"1.6.12"}. The log shows [launcher] starting 1.6.12, any [config] warnings, [db] schema is up to date (sqlite) and [server] version 1.6.12 listening on http://localhost:3001 (production).
The site is on port 8080 of the host over plain http. A real site needs HTTPS in front of it: see Put it behind HTTPS (proxy to localhost:8080) or Docker: HTTPS, ports and volumes.
Run the helper commands
The commands from What is in the download run inside the container:
docker exec choir-manager npm run config:check
docker exec choir-manager npm run admin:add -- someone@example.org "Their Name"
Stop, restart, remove
docker stop choir-manager
docker start choir-manager
docker rm -f choir-manager # removes the container; the choir-data volume stays
Changing the settings file means recreating the container (docker rm -f and docker run again), because the settings are read when it starts.
Updates
With a licence key, the admin panel can install a new version itself: the release is kept in the volume and used until the image is as new. See Update a Docker site.
Next
- One command to run it with SQLite: Docker Compose: one container with SQLite.
- With PostgreSQL and storage: Docker Compose: PostgreSQL and MinIO.
- After installing: first login and go-live checklist.