diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index b794c3a1..5caa2552 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -17,28 +17,6 @@ jobs: with: dockerfile: Dockerfile - javascript-lint: - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '23' - cache: 'npm' - cache-dependency-path: website/package.json - - - name: Install dependencies - run: cd website && npm ci - - - name: Run ESLint - uses: eslint/eslint-action@v3.0.0 - with: - workdir: website - security-scan: runs-on: ubuntu-latest timeout-minutes: 15 @@ -49,6 +27,12 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build Docker image uses: docker/build-push-action@v5 with: @@ -64,6 +48,7 @@ jobs: command: cves image: apostrophe-cms:test sarif-file: docker-scan-results.sarif + only-severities: critical,high - name: Upload scan results uses: github/codeql-action/upload-sarif@v3 diff --git a/Dockerfile b/Dockerfile index 85d4da60..4f911a2f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,9 @@ FROM node:23-alpine # Create app directory and set permissions WORKDIR /app +# Install dependencies needed for health checks with pinned version +RUN apk add --no-cache wget=1.25.0-r0 + # Create a non-root user and group RUN addgroup -S appgroup && adduser -S appuser -G appgroup @@ -10,7 +13,7 @@ RUN addgroup -S appgroup && adduser -S appuser -G appgroup COPY website/package.json website/package-lock.json* ./ # Install dependencies with specific flags for production -RUN npm ci --only=production && \ +RUN npm ci && \ # Clean npm cache to reduce image size npm cache clean --force diff --git a/README.md b/README.md new file mode 100644 index 00000000..b6253759 --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# Apostrophe CMS Docker Compose Setup + +This repository contains a complete Docker Compose setup for Apostrophe CMS with all required services. + +## Services + +- **Apostrophe CMS**: The main web application +- **MongoDB**: Database for Apostrophe +- **Redis**: Optional caching server for better performance +- **Mongo Express**: Web-based MongoDB admin interface + +## Quick Start + +1. Clone this repository +2. Start all services with Docker Compose: + +```bash +docker-compose up -d +``` + +3. Access the Apostrophe CMS: http://localhost:3000 +4. Access Mongo Express: http://localhost:8081 + +## Development + +For development with hot reloading, use: + +```bash +docker-compose -f docker-compose.yml -f docker-compose.override.yml up -d +``` + +## Environment Variables + +Environment variables are stored in the `.env` file. For production, you should change: + +- `SESSION_SECRET`: Set to a secure random string +- `NODE_ENV`: Change to `production` + +## Container Management + +- **Start containers**: `docker-compose up -d` +- **Stop containers**: `docker-compose down` +- **View logs**: `docker-compose logs -f` +- **Rebuild containers**: `docker-compose up -d --build` + +## Data Persistence + +MongoDB and Redis data are stored in Docker volumes for persistence between restarts: + +- `mongodb_data` +- `redis_data` + +To remove all data and start fresh: + +```bash +docker-compose down -v +``` \ No newline at end of file diff --git a/docker-compose.override.yml b/docker-compose.override.yml new file mode 100644 index 00000000..a8efbae2 --- /dev/null +++ b/docker-compose.override.yml @@ -0,0 +1,24 @@ +version: '3.8' + +services: + apostrophe: + # In development mode, use nodemon instead of normal start + command: npm run dev + environment: + - NODE_ENV=development + - REDIS_URI=redis://redis:6379 + ports: + - "9229:9229" # For Node.js debugging + volumes: + - ./website:/app + - /app/node_modules + + # Uncomment to enable Adminer for database management + # adminer: + # image: adminer:latest + # container_name: apostrophe-adminer + # ports: + # - "8080:8080" + # depends_on: + # - mongodb + # restart: unless-stopped \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..6fe9fe0e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,79 @@ +version: '3.8' + +services: + # Apostrophe CMS web application + apostrophe: + build: + context: . + dockerfile: Dockerfile + container_name: apostrophe-cms + ports: + - "3000:3000" + environment: + - NODE_ENV=development + - MONGODB_URI=mongodb://mongodb:27017/apostrophe + - SESSION_SECRET=change_this_to_a_secure_secret + volumes: + - ./website:/app + - /app/node_modules + depends_on: + - mongodb + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 15s + + # MongoDB database + mongodb: + image: mongo:7.0 + container_name: apostrophe-mongodb + ports: + - "27017:27017" + volumes: + - mongodb_data:/data/db + command: mongod --quiet --logpath /dev/null + restart: unless-stopped + healthcheck: + test: echo 'db.runCommand("ping").ok' | mongosh localhost:27017/test --quiet + interval: 10s + timeout: 5s + retries: 3 + start_period: 5s + + # Mongo Express for database management (optional, development only) + mongo-express: + image: mongo-express:latest + container_name: apostrophe-mongo-express + ports: + - "8081:8081" + environment: + - ME_CONFIG_MONGODB_SERVER=mongodb + - ME_CONFIG_MONGODB_PORT=27017 + - ME_CONFIG_BASICAUTH_USERNAME=admin + - ME_CONFIG_BASICAUTH_PASSWORD=password + depends_on: + - mongodb + restart: unless-stopped + + # Redis for caching (optional, but recommended for production) + redis: + image: redis:7-alpine + container_name: apostrophe-redis + ports: + - "6379:6379" + volumes: + - redis_data:/data + command: redis-server --appendonly yes + restart: unless-stopped + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 3 + +volumes: + mongodb_data: + redis_data: \ No newline at end of file diff --git a/website/app.js b/website/app.js new file mode 100644 index 00000000..2048f3fa --- /dev/null +++ b/website/app.js @@ -0,0 +1,50 @@ +const path = require('path'); + +require('apostrophe')({ + shortName: 'apostrophe-site', + baseUrl: process.env.BASE_URL || 'http://localhost:3000', + + // MongoDB connection string + mongo: { + uri: process.env.MONGODB_URI || 'mongodb://localhost:27017/apostrophe' + }, + + // Session configuration + modules: { + // Core modules configuration + '@apostrophecms/express': { + options: { + session: { + // If using Redis (recommended for production) + secret: process.env.SESSION_SECRET || 'changeme', + store: process.env.REDIS_URI ? { + connect: require('connect-redis'), + options: { + url: process.env.REDIS_URI || 'redis://localhost:6379' + } + } : {} + } + } + }, + + // Configure page types + '@apostrophecms/page': { + options: { + types: [ + { + name: 'default-page', + label: 'Default' + }, + { + name: 'home-page', + label: 'Home' + } + ] + } + }, + + // Project page types + 'default-page': {}, + 'home-page': {} + } +}); \ No newline at end of file diff --git a/website/modules/default-page/index.js b/website/modules/default-page/index.js new file mode 100644 index 00000000..62277f0e --- /dev/null +++ b/website/modules/default-page/index.js @@ -0,0 +1,28 @@ +module.exports = { + extend: '@apostrophecms/page-type', + options: { + label: 'Default Page' + }, + fields: { + add: { + main: { + type: 'area', + label: 'Main Content', + options: { + widgets: { + '@apostrophecms/rich-text': { + toolbar: [ 'styles', 'bold', 'italic', 'link', 'unlink' ] + }, + '@apostrophecms/image': {} + } + } + } + }, + group: { + basics: { + label: 'Basics', + fields: ['title', 'main'] + } + } + } +}; \ No newline at end of file diff --git a/website/modules/home-page/index.js b/website/modules/home-page/index.js new file mode 100644 index 00000000..a265e28d --- /dev/null +++ b/website/modules/home-page/index.js @@ -0,0 +1,42 @@ +module.exports = { + extend: '@apostrophecms/page-type', + options: { + label: 'Home Page' + }, + fields: { + add: { + main: { + type: 'area', + label: 'Main Content', + options: { + widgets: { + '@apostrophecms/rich-text': { + toolbar: [ 'styles', 'bold', 'italic', 'link', 'unlink' ] + }, + '@apostrophecms/image': {} + } + } + }, + hero: { + type: 'area', + label: 'Hero Section', + options: { + widgets: { + '@apostrophecms/rich-text': {}, + '@apostrophecms/image': {} + } + } + } + }, + group: { + basics: { + label: 'Basics', + fields: ['title', 'main'] + }, + hero: { + label: 'Hero', + fields: ['hero'] + } + } + } +}; \ No newline at end of file diff --git a/website/package-lock.json b/website/package-lock.json index 81ece229..903164e0 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "apostrophe": "^4.14.2", + "connect-redis": "^7.1.1", "express": "^5.1.0", "lodash": "^4.17.21" }, @@ -3916,6 +3917,18 @@ "node": ">=0.6" } }, + "node_modules/connect-redis": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-7.1.1.tgz", + "integrity": "sha512-M+z7alnCJiuzKa8/1qAYdGUXHYfDnLolOGAUjOioB07pP39qxjG+X9ibsud7qUBc4jMV5Mcy3ugGv8eFcgamJQ==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "express-session": ">=1" + } + }, "node_modules/content-disposition": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", diff --git a/website/package.json b/website/package.json index 68085f02..8fc8931c 100644 --- a/website/package.json +++ b/website/package.json @@ -34,7 +34,8 @@ "dependencies": { "apostrophe": "^4.14.2", "express": "^5.1.0", - "lodash": "^4.17.21" + "lodash": "^4.17.21", + "connect-redis": "^7.1.1" }, "devDependencies": { "eslint": "^9.24.0",