How to Self-Host Apostrophe CMS with Docker Compose

What Is Apostrophe CMS?

Apostrophe CMS is an open-source content management framework built with Node.js and MongoDB. Its defining feature is in-context editing — you edit content directly on the page as it appears to visitors, rather than in a separate admin panel. It uses a module-based architecture that supports both traditional server-rendered sites and headless API-driven frontends. Licensed under MIT, making it fully open source for personal and commercial use. Official site.

Prerequisites

  • A Linux server (Ubuntu 22.04+ recommended)
  • Docker and Docker Compose installed (guide)
  • 1 GB of free disk space
  • 1 GB of RAM minimum (2 GB recommended)
  • Node.js 22+ and npm (for initial project creation)
  • A domain name (optional, for remote access)

Docker Compose Configuration

First, create a new Apostrophe project:

npx create-apostrophe-app ~/apostrophe-site
cd ~/apostrophe-site

Create a Dockerfile in the project root:

FROM node:22-alpine

WORKDIR /srv/www/apostrophe

RUN chown -R node:node /srv/www/apostrophe

USER node

COPY --chown=node package*.json ./
ENV NODE_ENV=production
RUN npm ci --production

COPY --chown=node . .

RUN APOS_RELEASE_ID=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1) \
    && echo $APOS_RELEASE_ID > ./release-id \
    && node app @apostrophecms/asset:build

EXPOSE 3000

CMD ["node", "app.js"]

Create docker-compose.yml:

services:
  apostrophe:
    build: .
    container_name: apostrophe
    ports:
      - "3000:3000"
    volumes:
      - apostrophe_uploads:/srv/www/apostrophe/public/uploads
    environment:
      - NODE_ENV=production
      - APOS_MONGODB_URI=mongodb://apostrophe-db:27017/apostrophe
      - APOS_CLUSTER_PROCESSES=2
    depends_on:
      apostrophe-db:
        condition: service_healthy
    restart: unless-stopped

  apostrophe-db:
    image: mongo:8.0
    container_name: apostrophe-db
    volumes:
      - apostrophe_dbdata:/data/db
    healthcheck:
      test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  apostrophe_uploads:
  apostrophe_dbdata:

Build and start:

docker compose build
docker compose up -d

Create the admin user:

docker compose exec apostrophe node app @apostrophecms/user:add admin admin

This creates a user named “admin” with the “admin” role. You’ll be prompted to set a password.

Initial Setup

  1. Open http://your-server-ip:3000 in your browser
  2. Click the “Log In” button (typically in the admin bar)
  3. Log in with the admin credentials you created
  4. The admin bar appears at the top — click “New” to create pages and content
  5. Edit content directly on the page by clicking any editable region

Configuration

SettingEnvironment VariableDescription
MongoDB URIAPOS_MONGODB_URIConnection string for MongoDB
Cluster processesAPOS_CLUSTER_PROCESSESWorker count for Node.js clustering (set to 2 for availability)
Node environmentNODE_ENVproduction or development
External frontendAPOS_EXTERNAL_FRONT_KEYAPI key for headless/external frontend access
Public hostAPOS_HOSTPublic URL for the site

Module Architecture

Apostrophe uses a module-based system. Core modules handle pages, pieces (structured content), widgets, and areas. Custom modules extend functionality:

// modules/article/index.js
module.exports = {
  extend: '@apostrophecms/piece-type',
  options: {
    label: 'Article',
    pluralLabel: 'Articles'
  },
  fields: {
    add: {
      body: {
        type: 'area',
        options: {
          widgets: {
            '@apostrophecms/rich-text': {},
            '@apostrophecms/image': {}
          }
        }
      },
      publishDate: {
        type: 'date',
        label: 'Publish Date'
      }
    }
  }
};

Register modules in app.js:

require('apostrophe')({
  shortName: 'my-site',
  modules: {
    article: {}
  }
});

S3 Asset Storage

For production deployments, store uploaded assets in S3-compatible storage:

    environment:
      - APOS_S3_BUCKET=my-bucket
      - APOS_S3_REGION=us-east-1
      - APOS_S3_KEY=access-key
      - APOS_S3_SECRET=secret-key
      - APOS_S3_ENDPOINT=https://s3.amazonaws.com

This also works with MinIO, Wasabi, or any S3-compatible provider.

Reverse Proxy

Example Nginx Proxy Manager configuration:

  • Scheme: http
  • Forward Hostname: apostrophe
  • Forward Port: 3000

See Reverse Proxy Setup for full configuration guides.

Backup

Back up these volumes:

  • apostrophe_dbdata — MongoDB database with all content, users, and configuration
  • apostrophe_uploads — uploaded media files

Use mongodump for consistent backups:

docker compose exec apostrophe-db mongodump --out /data/backup
docker cp apostrophe-db:/data/backup ./mongo-backup-$(date +%Y%m%d)

See Backup Strategy for automated approaches.

Troubleshooting

Admin Bar Not Appearing

Symptom: Site loads but no admin bar or login option visible. Fix: Navigate to /login directly. Verify the admin user exists: docker compose exec apostrophe node app @apostrophecms/user:list. If no users exist, create one with the user:add command.

Asset Build Errors During Docker Build

Symptom: Dockerfile build fails at the asset build step. Fix: Ensure APOS_RELEASE_ID is generated before the build command. Check Node.js version compatibility — Apostrophe v4 requires Node.js 18+. Clear the build cache: docker compose build --no-cache.

MongoDB Connection Timeout

Symptom: Apostrophe exits with “MongoServerSelectionError: connect ECONNREFUSED”. Fix: Ensure MongoDB is fully started before Apostrophe. The depends_on with service_healthy handles this. For external MongoDB, verify the connection string format: mongodb://host:27017/database.

In-Context Editing Not Working

Symptom: Clicking editable areas doesn’t open the editor. Fix: Check browser console for JavaScript errors. Ensure assets were built correctly during the Docker build. If using a CDN or reverse proxy, verify that all Apostrophe asset URLs are accessible.

Resource Requirements

  • RAM: ~200 MB idle, ~500 MB under load with clustering
  • CPU: Low-Medium — Node.js handles requests efficiently
  • Disk: ~150 MB for application, plus media uploads and MongoDB storage

Verdict

Apostrophe CMS stands out with its in-context editing experience — editing content directly on the rendered page is more intuitive than any separate admin panel. The module architecture is clean and extensible, and the MIT license means no licensing costs. The trade-off is ecosystem size: Apostrophe’s plugin and theme library is tiny compared to WordPress or even Ghost. MongoDB as a requirement adds operational complexity compared to flat-file systems like Statamic. Choose Apostrophe if in-context editing is a priority and you’re comfortable with Node.js and MongoDB. For a simpler Node.js CMS with a larger community, Ghost is the safer choice.

Comments