Compare commits

..

4 Commits

Author SHA1 Message Date
Markos Gogoulos
aeef8284bf docs: update page link 2025-12-01 11:29:58 +02:00
Markos Gogoulos
a90fcbf8dd version bump 2025-11-21 12:30:12 +02:00
Markos Gogoulos
1b3cdfd302 fix: add delay to task creation 2025-11-21 12:30:05 +02:00
Yiannis Christodoulou
cd7dd4f72c fix: Chapter numbering and preserve custom titles on segment reorder (#1435)
* FIX: Preserve custom chapter titles when renumbering (151)

Updated the renumberAllSegments function to only update chapter titles that match the default 'Chapter X' pattern, preserving any custom titles. Also ensured segments are renumbered after updates for consistent chronological naming.

* build assets (chapters editor)
2025-11-21 12:29:19 +02:00
43 changed files with 532 additions and 1783 deletions

View File

@@ -1,113 +0,0 @@
FROM python:3.13.5-slim-bookworm AS build-image
# Install system dependencies needed for downloading and extracting
RUN apt-get update -y && \
apt-get install -y --no-install-recommends wget xz-utils unzip && \
rm -rf /var/lib/apt/lists/* && \
apt-get purge --auto-remove && \
apt-get clean
RUN wget -q https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz
RUN mkdir -p ffmpeg-tmp && \
tar -xf ffmpeg-release-amd64-static.tar.xz --strip-components 1 -C ffmpeg-tmp && \
cp -v ffmpeg-tmp/ffmpeg ffmpeg-tmp/ffprobe ffmpeg-tmp/qt-faststart /usr/local/bin && \
rm -rf ffmpeg-tmp ffmpeg-release-amd64-static.tar.xz
# Install Bento4 in the specified location
RUN mkdir -p /home/mediacms.io/bento4 && \
wget -q http://zebulon.bok.net/Bento4/binaries/Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip && \
unzip Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip -d /home/mediacms.io/bento4 && \
mv /home/mediacms.io/bento4/Bento4-SDK-1-6-0-637.x86_64-unknown-linux/* /home/mediacms.io/bento4/ && \
rm -rf /home/mediacms.io/bento4/Bento4-SDK-1-6-0-637.x86_64-unknown-linux && \
rm -rf /home/mediacms.io/bento4/docs && \
rm Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip
############ BASE RUNTIME IMAGE ############
FROM python:3.13.5-slim-bookworm AS base
SHELL ["/bin/bash", "-c"]
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
ENV CELERY_APP='cms'
ENV VIRTUAL_ENV=/home/mediacms.io
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
# Install system dependencies first
RUN apt-get update -y && \
apt-get -y upgrade && \
apt-get install --no-install-recommends -y \
supervisor \
nginx \
imagemagick \
procps \
build-essential \
pkg-config \
zlib1g-dev \
zlib1g \
libxml2-dev \
libxmlsec1-dev \
libxmlsec1-openssl \
libpq-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Set up virtualenv first
RUN mkdir -p /home/mediacms.io/mediacms/{logs} && \
cd /home/mediacms.io && \
python3 -m venv $VIRTUAL_ENV
# Copy requirements files
COPY requirements.txt requirements-dev.txt ./
# Install Python dependencies using pip (within virtualenv)
ARG DEVELOPMENT_MODE=False
RUN pip install --no-cache-dir uv && \
uv pip install --no-binary lxml --no-binary xmlsec -r requirements.txt && \
if [ "$DEVELOPMENT_MODE" = "True" ]; then \
echo "Installing development dependencies..." && \
uv pip install -r requirements-dev.txt; \
fi && \
apt-get purge -y --auto-remove \
build-essential \
pkg-config \
libxml2-dev \
libxmlsec1-dev \
libpq-dev
# Copy ffmpeg and Bento4 from build image
COPY --from=build-image /usr/local/bin/ffmpeg /usr/local/bin/ffmpeg
COPY --from=build-image /usr/local/bin/ffprobe /usr/local/bin/ffprobe
COPY --from=build-image /usr/local/bin/qt-faststart /usr/local/bin/qt-faststart
COPY --from=build-image /home/mediacms.io/bento4 /home/mediacms.io/bento4
# Copy application files
COPY . /home/mediacms.io/mediacms
WORKDIR /home/mediacms.io/mediacms
# required for sprite thumbnail generation for large video files
COPY deploy/docker/policy.xml /etc/ImageMagick-6/policy.xml
# Set process control environment variables
ENV ENABLE_UWSGI='yes' \
ENABLE_NGINX='yes' \
ENABLE_CELERY_BEAT='yes' \
ENABLE_CELERY_SHORT='yes' \
ENABLE_CELERY_LONG='yes' \
ENABLE_MIGRATIONS='yes'
EXPOSE 9000 80
RUN chmod +x ./deploy/docker/entrypoint.sh
ENTRYPOINT ["./deploy/docker/entrypoint.sh"]
CMD ["./deploy/docker/start.sh"]
############ FULL IMAGE ############
FROM base AS full
COPY requirements-full.txt ./
RUN mkdir -p /root/.cache/ && \
chmod go+rwx /root/ && \
chmod go+rwx /root/.cache/
RUN uv pip install -r requirements-full.txt

View File

@@ -1,119 +0,0 @@
version: "3"
services:
nginx-proxy:
image: nginxproxy/nginx-proxy
container_name: nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- conf:/etc/nginx/conf.d
- vhost:/etc/nginx/vhost.d
- html:/usr/share/nginx/html
- dhparam:/etc/nginx/dhparam
- certs:/etc/nginx/certs:ro
- /var/run/docker.sock:/tmp/docker.sock:ro
- ./deploy/docker/reverse_proxy/client_max_body_size.conf:/etc/nginx/conf.d/client_max_body_size.conf:ro
acme-companion:
image: nginxproxy/acme-companion
container_name: nginx-proxy-acme
volumes_from:
- nginx-proxy
volumes:
- certs:/etc/nginx/certs:rw
- acme:/etc/acme.sh
- /var/run/docker.sock:/var/run/docker.sock:ro
migrations:
image: mediacms/mediacms:latest
volumes:
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_CELERY_BEAT: 'no'
ADMIN_USER: 'admin'
ADMIN_EMAIL: 'Y'
ADMIN_PASSWORD: 'X'
command: "./deploy/docker/prestart.sh"
restart: on-failure
depends_on:
redis:
condition: service_healthy
db:
condition: service_healthy
web:
image: mediacms/mediacms:latest
deploy:
replicas: 1
volumes:
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_CELERY_BEAT: 'no'
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_MIGRATIONS: 'no'
VIRTUAL_HOST: 'X.mediacms.io'
LETSENCRYPT_HOST: 'X.mediacms.io'
LETSENCRYPT_EMAIL: 'X'
depends_on:
- migrations
celery_beat:
image: mediacms/mediacms:latest
volumes:
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_MIGRATIONS: 'no'
depends_on:
- redis
celery_worker:
image: mediacms/mediacms:full
deploy:
replicas: 1
volumes:
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_BEAT: 'no'
ENABLE_MIGRATIONS: 'no'
depends_on:
- migrations
db:
image: postgres:17.2-alpine
volumes:
- ../postgres_data:/var/lib/postgresql/data/
restart: always
environment:
POSTGRES_USER: mediacms
POSTGRES_PASSWORD: mediacms
POSTGRES_DB: mediacms
TZ: Europe/London
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: "redis:alpine"
restart: always
healthcheck:
test: ["CMD", "redis-cli","ping"]
interval: 10s
timeout: 5s
retries: 3
volumes:
conf:
vhost:
html:
dhparam:
certs:
acme:

View File

@@ -1,89 +0,0 @@
version: "3"
services:
migrations:
build:
context: .
dockerfile: ./Dockerfile
target: base
args:
- DEVELOPMENT_MODE=True
image: mediacms/mediacms-dev:latest
volumes:
- ./:/home/mediacms.io/mediacms/
command: "./deploy/docker/prestart.sh"
environment:
DEVELOPMENT_MODE: True
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_CELERY_BEAT: 'no'
ADMIN_USER: 'admin'
ADMIN_EMAIL: 'admin@localhost'
ADMIN_PASSWORD: 'admin'
restart: on-failure
depends_on:
redis:
condition: service_healthy
db:
condition: service_healthy
frontend:
image: node:20
volumes:
- ${PWD}/frontend:/home/mediacms.io/mediacms/frontend/
working_dir: /home/mediacms.io/mediacms/frontend/
command: bash -c "npm install && npm run start"
env_file:
- ${PWD}/frontend/.env
ports:
- "8088:8088"
depends_on:
- web
web:
image: mediacms/mediacms-dev:latest
command: "python manage.py runserver 0.0.0.0:80"
environment:
DEVELOPMENT_MODE: True
ports:
- "80:80"
volumes:
- ./:/home/mediacms.io/mediacms/
depends_on:
- migrations
db:
image: postgres:17.2-alpine
volumes:
- ../postgres_data:/var/lib/postgresql/data/
restart: always
environment:
POSTGRES_USER: mediacms
POSTGRES_PASSWORD: mediacms
POSTGRES_DB: mediacms
TZ: Europe/London
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: "redis:alpine"
restart: always
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 10s
retries: 3
celery_worker:
image: mediacms/mediacms-dev:latest
deploy:
replicas: 1
volumes:
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_BEAT: 'no'
ENABLE_MIGRATIONS: 'no'
depends_on:
- web

View File

@@ -1,86 +0,0 @@
version: "3"
services:
migrations:
image: mediacms/mediacms:latest
volumes:
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_CELERY_BEAT: 'no'
ADMIN_USER: 'admin'
ADMIN_EMAIL: 'admin@localhost'
# ADMIN_PASSWORD: 'uncomment_and_set_password_here'
command: "./deploy/docker/prestart.sh"
restart: on-failure
depends_on:
redis:
condition: service_healthy
db:
condition: service_healthy
web:
image: mediacms/mediacms:latest
deploy:
replicas: 1
ports:
- "80:80"
volumes:
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_CELERY_BEAT: 'no'
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_MIGRATIONS: 'no'
depends_on:
- migrations
celery_beat:
image: mediacms/mediacms:latest
volumes:
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_MIGRATIONS: 'no'
depends_on:
- redis
celery_worker:
image: mediacms/mediacms:latest
deploy:
replicas: 1
volumes:
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_BEAT: 'no'
ENABLE_MIGRATIONS: 'no'
depends_on:
- migrations
db:
image: postgres:17.2-alpine
volumes:
- ../postgres_data:/var/lib/postgresql/data/
restart: always
environment:
POSTGRES_USER: mediacms
POSTGRES_PASSWORD: mediacms
POSTGRES_DB: mediacms
TZ: Europe/London
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: "redis:alpine"
restart: always
healthcheck:
test: ["CMD", "redis-cli","ping"]
interval: 10s
timeout: 5s
retries: 3

View File

@@ -1,37 +1,2 @@
# Dependencies
node_modules
npm-debug.log
# Local development files - exclude uploaded content but keep placeholder images
media_files/*
!media_files/userlogos/
media_files/userlogos/*
!media_files/userlogos/*.jpg
logs
static_collected
# Version control
.git
.github
.gitignore
# Development/testing
.pytest_cache
.qodo
.claude
# Docker
.dockerignore
Dockerfile
docker-compose*.yml
.docker-backup
# Documentation (if you don't need it in the image)
docs
# Other
*.pyc
__pycache__
.env
.vscode
.idea
npm-debug.log

View File

@@ -21,8 +21,8 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker meta for web image
id: meta-web
- name: Docker meta for base image
id: meta-base
uses: docker/metadata-action@v4
with:
images: |
@@ -40,95 +40,39 @@ jobs:
org.opencontainers.image.source=https://github.com/mediacms-io/mediacms
org.opencontainers.image.licenses=AGPL-3.0
- name: Build and push web image
uses: docker/build-push-action@v4
with:
context: .
target: web
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta-web.outputs.tags }}
labels: ${{ steps.meta-web.outputs.labels }}
- name: Docker meta for worker image
id: meta-worker
- name: Docker meta for full image
id: meta-full
uses: docker/metadata-action@v4
with:
images: |
mediacms/mediacms-worker
mediacms/mediacms
tags: |
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
labels: |
org.opencontainers.image.title=MediaCMS Worker
org.opencontainers.image.description=MediaCMS Celery worker for background task processing.
org.opencontainers.image.vendor=MediaCMS
org.opencontainers.image.url=https://mediacms.io/
org.opencontainers.image.source=https://github.com/mediacms-io/mediacms
org.opencontainers.image.licenses=AGPL-3.0
- name: Build and push worker image
uses: docker/build-push-action@v4
with:
context: .
target: worker
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta-worker.outputs.tags }}
labels: ${{ steps.meta-worker.outputs.labels }}
- name: Docker meta for worker-full image
id: meta-worker-full
uses: docker/metadata-action@v4
with:
images: |
mediacms/mediacms-worker
tags: |
type=raw,value=latest-full,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
type=raw,value=full,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
type=semver,pattern={{version}}-full
type=semver,pattern={{major}}.{{minor}}-full
type=semver,pattern={{major}}-full
labels: |
org.opencontainers.image.title=MediaCMS Worker Full
org.opencontainers.image.description=MediaCMS Celery worker with additional codecs for advanced transcoding features.
org.opencontainers.image.title=MediaCMS Full
org.opencontainers.image.description=MediaCMS is a modern, fully featured open source video and media CMS, written in Python/Django and React, featuring a REST API. This is the full version with additional dependencies.
org.opencontainers.image.vendor=MediaCMS
org.opencontainers.image.url=https://mediacms.io/
org.opencontainers.image.source=https://github.com/mediacms-io/mediacms
org.opencontainers.image.licenses=AGPL-3.0
- name: Build and push worker-full image
- name: Build and push full image
uses: docker/build-push-action@v4
with:
context: .
target: worker-full
target: full
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta-worker-full.outputs.tags }}
labels: ${{ steps.meta-worker-full.outputs.labels }}
tags: ${{ steps.meta-full.outputs.tags }}
labels: ${{ steps.meta-full.outputs.labels }}
- name: Docker meta for nginx image
id: meta-nginx
uses: docker/metadata-action@v4
with:
images: |
mediacms/mediacms-nginx
tags: |
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
labels: |
org.opencontainers.image.title=MediaCMS Nginx
org.opencontainers.image.description=Nginx web server for MediaCMS, serving static and media files.
org.opencontainers.image.vendor=MediaCMS
org.opencontainers.image.url=https://mediacms.io/
org.opencontainers.image.source=https://github.com/mediacms-io/mediacms
org.opencontainers.image.licenses=AGPL-3.0
- name: Build and push nginx image
- name: Build and push base image
uses: docker/build-push-action@v4
with:
context: .
file: Dockerfile.nginx
target: base
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta-nginx.outputs.tags }}
labels: ${{ steps.meta-nginx.outputs.labels }}
tags: ${{ steps.meta-base.outputs.tags }}
labels: ${{ steps.meta-base.outputs.labels }}

2
.gitignore vendored
View File

@@ -17,7 +17,7 @@ static/mptt/
static/rest_framework/
static/drf-yasg
cms/local_settings.py
config/local_settings.py
deploy/docker/local_settings.py
yt.readme.md
/frontend-tools/video-editor/node_modules
/frontend-tools/video-editor/client/node_modules

View File

@@ -1,254 +0,0 @@
# MediaCMS Docker Restructure Summary
## Overview
This document summarizes the complete Docker architecture restructure for MediaCMS 7.3, eliminating supervisord and implementing modern Docker best practices.
## What Was Created
### New Files
#### Dockerfiles
- `Dockerfile` - Multi-stage Dockerfile with targets (replaced old Dockerfile):
- `build-image` - FFmpeg and Bento4 builder
- `base` - Python/Django base image
- `web` - uWSGI web server
- `worker` - Celery worker (standard)
- `worker-full` - Celery worker with extra codecs
- `Dockerfile.nginx` - Vanilla nginx with MediaCMS configs baked in
#### Docker Compose Files
- `docker-compose.yaml` - Production deployment (no file mounts) - REPLACED
- `docker-compose-cert.yaml` - Production with HTTPS (Let's Encrypt) - REPLACED
- `docker-compose-dev.yaml` - Development with file mounts and hot reload - REPLACED
#### Scripts
- `scripts/entrypoint-web.sh` - Web container entrypoint
- `scripts/entrypoint-worker.sh` - Worker container entrypoint
- `scripts/run-migrations.sh` - Migration runner script
#### Configuration
- `config/nginx/nginx.conf` - Main nginx config (from deploy/docker/)
- `config/nginx/site.conf` - Virtual host config (from deploy/docker/nginx_http_only.conf)
- `config/nginx/uwsgi_params` - uWSGI params (from deploy/docker/)
- `config/nginx-proxy/client_max_body_size.conf` - For nginx-proxy (from deploy/docker/reverse_proxy/)
- `config/uwsgi/uwsgi.ini` - uWSGI configuration (from deploy/docker/)
- `config/imagemagick/policy.xml` - ImageMagick policy (from deploy/docker/)
#### Documentation
- `docs/DOCKER_V7.3_MIGRATION.md` - Complete migration guide
- Updated `docs/admins_docs.md` - Sections 4 and 5
## Architecture Changes
### Before (Old Architecture)
```
Single Container (supervisord managing multiple processes)
├── nginx (port 80)
├── uwsgi (port 9000)
├── celery beat
├── celery short workers
└── celery long workers
Controlled by ENABLE_* environment variables
```
### After (New Architecture)
```
Dedicated Containers (one process per container)
├── nginx (port 80) → web:9000
├── web (uwsgi on port 9000)
├── celery_beat
├── celery_short (scalable)
├── celery_long (scalable, optional :full image)
├── migrations (runs on startup)
├── db (PostgreSQL)
└── redis
Volumes:
- static_files (nginx ← web)
- media_files (nginx ← web, workers)
- postgres_data
```
## Key Improvements
### 1. **Removed Components**
- ❌ supervisord and all configs in `deploy/docker/supervisord/`
-`deploy/docker/start.sh`
-`deploy/docker/entrypoint.sh`
- ❌ All `ENABLE_*` environment variables
### 2. **Separated Services**
- Nginx runs in its own container
- Django/uWSGI in dedicated web container
- Celery workers split by task duration
- Migrations run automatically on every startup
### 3. **Production Ready**
- No file mounts in production (immutable images)
- Named volumes for data persistence
- Proper health checks
- Individual service scaling
### 4. **Development Friendly**
- Separate `-dev` compose file with file mounts
- Django debug mode
- Frontend hot reload
- Live code editing
## Images to Build
For production, these images need to be built and pushed to Docker Hub:
```bash
# Build base and web image
docker build --target web -t mediacms/mediacms:7.3 .
# Build worker image
docker build --target worker -t mediacms/mediacms-worker:7.3 .
# Build worker-full image
docker build --target worker-full -t mediacms/mediacms-worker:7.3-full .
# Build nginx image
docker build -f Dockerfile.nginx -t mediacms/mediacms-nginx:7.3 .
```
## Deployment Options
### 1. Development
```bash
docker compose -f docker-compose-dev.yaml up
```
- File mounts for live editing
- Django runserver
- Frontend dev server
### 2. Production (HTTP)
```bash
# Rename .new files first
mv docker-compose.yaml.new docker-compose.yaml
docker compose up -d
```
- Immutable images
- No file mounts
- Port 80
### 3. Production (HTTPS)
```bash
# Rename .new files first
mv docker-compose-cert.yaml.new docker-compose-cert.yaml
# Edit and set your domain/email
docker compose -f docker-compose-cert.yaml up -d
```
- Automatic Let's Encrypt certificates
- Auto-renewal
## Migration Path for Existing Systems
### For Production Systems Currently Running
1. **Backup first**
```bash
docker exec <db_container> pg_dump -U mediacms mediacms > backup.sql
```
2. **Update compose file**
- Replace old docker-compose files with new ones
- Update domain settings in cert file if using HTTPS
3. **Pull new images**
```bash
docker pull mediacms/mediacms:7.3
docker pull mediacms/mediacms-worker:7.3
docker pull mediacms/mediacms-nginx:7.3
```
4. **Restart**
```bash
docker compose down
docker compose up -d
```
### Breaking Changes
1. **No more ENABLE_* variables** - Remove from any custom configs
2. **deploy/docker/local_settings.py** - Now use environment variables or custom image
3. **Service names changed**:
- `celery_worker` → `celery_short` + `celery_long`
- Added `nginx` service
## Testing Checklist
Before deploying to production, test:
- [ ] Migrations run successfully
- [ ] Static files served correctly
- [ ] Media files served correctly
- [ ] Django admin accessible
- [ ] Video upload works
- [ ] Video transcoding works (celery_long)
- [ ] Thumbnail generation works (celery_short)
- [ ] HTTPS redirects work (if using cert file)
- [ ] Database persistence across restarts
- [ ] Media files persistence across restarts
## Configuration Examples
### Use Full Worker Image
```yaml
celery_long:
image: mediacms/mediacms-worker:7.3-full
```
### Set Custom Domain
```yaml
environment:
FRONTEND_HOST: 'https://videos.example.com'
PORTAL_NAME: 'My Video Portal'
```
### Scale Workers
```bash
docker compose up -d --scale celery_short=3 --scale celery_long=2
```
## Files to Review Before Finalizing
1. **Dockerfile** - Review Python/Django/uWSGI configuration
2. **config/nginx/site.conf** - Review nginx paths and proxy settings
3. **docker-compose.yaml** - Review volume mounts and service dependencies
4. **scripts/run-migrations.sh** - Review migration logic
## Next Steps
To finalize this restructure:
1. **Test locally** with docker-compose-dev.yaml
2. **Build images** and push to Docker Hub
3. **Update CI/CD** to build new images
4. **Test in staging environment**
5. **Create release notes** referencing migration guide
## Backup
Old Docker files have been backed up to `.docker-backup/` directory.
## Rollback Plan
If issues arise, rollback by:
1. Reverting to old docker-compose files
2. Using old image tags
3. Restoring database from backup if needed
Old files are preserved in `.docker-backup/` directory.
## Support
- Migration Guide: `docs/DOCKER_V7.3_MIGRATION.md`
- Admin Docs: `docs/admins_docs.md` (updated sections 4, 5)
- Issues: https://github.com/mediacms-io/mediacms/issues

View File

@@ -26,22 +26,20 @@ RUN mkdir -p /home/mediacms.io/bento4 && \
############ BASE RUNTIME IMAGE ############
FROM python:3.13.5-slim-bookworm AS base
LABEL org.opencontainers.image.version="7.3"
LABEL org.opencontainers.image.title="MediaCMS"
LABEL org.opencontainers.image.description="Modern, scalable and open source video platform"
SHELL ["/bin/bash", "-c"]
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
CELERY_APP='cms' \
VIRTUAL_ENV=/home/mediacms.io \
PATH="$VIRTUAL_ENV/bin:$PATH"
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
ENV CELERY_APP='cms'
ENV VIRTUAL_ENV=/home/mediacms.io
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
# Install system dependencies (no nginx, no supervisor)
# Install system dependencies first
RUN apt-get update -y && \
apt-get -y upgrade && \
apt-get install --no-install-recommends -y \
supervisor \
nginx \
imagemagick \
procps \
build-essential \
@@ -55,8 +53,8 @@ RUN apt-get update -y && \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Set up virtualenv
RUN mkdir -p /home/mediacms.io/mediacms/{logs,media_files,static_files} && \
# Set up virtualenv first
RUN mkdir -p /home/mediacms.io/mediacms/{logs} && \
cd /home/mediacms.io && \
python3 -m venv $VIRTUAL_ENV
@@ -84,51 +82,32 @@ COPY --from=build-image /usr/local/bin/ffprobe /usr/local/bin/ffprobe
COPY --from=build-image /usr/local/bin/qt-faststart /usr/local/bin/qt-faststart
COPY --from=build-image /home/mediacms.io/bento4 /home/mediacms.io/bento4
# Copy application files with correct ownership
COPY --chown=www-data:www-data . /home/mediacms.io/mediacms
# Copy application files
COPY . /home/mediacms.io/mediacms
WORKDIR /home/mediacms.io/mediacms
# Copy imagemagick policy for sprite thumbnail generation
COPY config/imagemagick/policy.xml /etc/ImageMagick-6/policy.xml
# required for sprite thumbnail generation for large video files
COPY deploy/docker/policy.xml /etc/ImageMagick-6/policy.xml
# Copy local_settings.py from config to cms/ for default Docker config (if exists)
RUN cp config/local_settings.py cms/local_settings.py 2>/dev/null || true
# Set process control environment variables
ENV ENABLE_UWSGI='yes' \
ENABLE_NGINX='yes' \
ENABLE_CELERY_BEAT='yes' \
ENABLE_CELERY_SHORT='yes' \
ENABLE_CELERY_LONG='yes' \
ENABLE_MIGRATIONS='yes'
# Create www-data user directories and set permissions
RUN mkdir -p /var/run/mediacms && \
chown www-data:www-data /var/run/mediacms
EXPOSE 9000 80
############ WEB IMAGE (Django/uWSGI) ############
FROM base AS web
RUN chmod +x ./deploy/docker/entrypoint.sh
# Install uWSGI
RUN uv pip install uwsgi
# Copy uWSGI configuration
COPY config/uwsgi/uwsgi.ini /home/mediacms.io/mediacms/uwsgi.ini
USER www-data
EXPOSE 9000
CMD ["/home/mediacms.io/bin/uwsgi", "--ini", "/home/mediacms.io/mediacms/uwsgi.ini"]
############ WORKER IMAGE (Celery) ############
FROM base AS worker
USER www-data
# CMD will be overridden in docker-compose for different worker types
############ FULL WORKER IMAGE (Celery with extra codecs) ############
FROM worker AS worker-full
USER root
ENTRYPOINT ["./deploy/docker/entrypoint.sh"]
CMD ["./deploy/docker/start.sh"]
############ FULL IMAGE ############
FROM base AS full
COPY requirements-full.txt ./
RUN mkdir -p /root/.cache/ && \
chmod go+rwx /root/ && \
chmod go+rwx /root/.cache/ && \
uv pip install -r requirements-full.txt
USER www-data
chmod go+rwx /root/.cache/
RUN uv pip install -r requirements-full.txt

View File

@@ -1,18 +0,0 @@
FROM nginx:alpine
LABEL org.opencontainers.image.version="7.3"
LABEL org.opencontainers.image.title="MediaCMS Nginx"
LABEL org.opencontainers.image.description="Nginx server for MediaCMS"
# Copy nginx configurations
COPY config/nginx/nginx.conf /etc/nginx/nginx.conf
COPY config/nginx/site.conf /etc/nginx/conf.d/default.conf
COPY config/nginx/uwsgi_params /etc/nginx/uwsgi_params
# Create directories for static and media files (will be volumes)
RUN mkdir -p /var/www/media /var/www/static && \
chown -R nginx:nginx /var/www
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -69,7 +69,7 @@ Copyright Markos Gogoulos.
## Support and paid services
We provide custom installations, development of extra functionality, migration from existing systems, integrations with legacy systems, training and support. Contact us at info@mediacms.io for more information.
We provide custom installations, development of extra functionality, migration from existing systems, integrations with legacy systems, training and support. Checkout our [services page](https://mediacms.io/#services/) for more information.
### Commercial Hostings
**Elestio**

View File

@@ -1 +1 @@
VERSION = "7.2.1"
VERSION = "7.2.2"

View File

@@ -1,7 +1,7 @@
# MediaCMS: Document Changes for DEIC
## Configuration Changes
The following changes are required in `config/local_settings.py`:
The following changes are required in `deploy/docker/local_settings.py`:
```python

3
deploy/docker/README.md Normal file
View File

@@ -0,0 +1,3 @@
# MediaCMS on Docker
See: [Details](../../docs/Docker_deployment.md)

38
deploy/docker/entrypoint.sh Executable file
View File

@@ -0,0 +1,38 @@
#!/bin/bash
set -e
# forward request and error logs to docker log collector
ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log && \
ln -sf /dev/stdout /var/log/nginx/mediacms.io.access.log && ln -sf /dev/stderr /var/log/nginx/mediacms.io.error.log
cp /home/mediacms.io/mediacms/deploy/docker/local_settings.py /home/mediacms.io/mediacms/cms/local_settings.py
mkdir -p /home/mediacms.io/mediacms/{logs,media_files/hls}
touch /home/mediacms.io/mediacms/logs/debug.log
mkdir -p /var/run/mediacms
chown www-data:www-data /var/run/mediacms
TARGET_GID=$(stat -c "%g" /home/mediacms.io/mediacms/)
EXISTS=$(cat /etc/group | grep $TARGET_GID | wc -l)
# Create new group using target GID and add www-data user
if [ $EXISTS == "0" ]; then
groupadd -g $TARGET_GID tempgroup
usermod -a -G tempgroup www-data
else
# GID exists, find group name and add
GROUP=$(getent group $TARGET_GID | cut -d: -f1)
usermod -a -G $GROUP www-data
fi
# We should do this only for folders that have a different owner, since it is an expensive operation
# Also ignoring .git folder to fix this issue https://github.com/mediacms-io/mediacms/issues/934
# Exclude package-lock.json files that may not exist or be removed during frontend setup
find /home/mediacms.io/mediacms ! \( -path "*.git*" -o -name "package-lock.json" \) -exec chown www-data:$TARGET_GID {} + 2>/dev/null || true
chmod +x /home/mediacms.io/mediacms/deploy/docker/start.sh /home/mediacms.io/mediacms/deploy/docker/prestart.sh
exec "$@"

View File

@@ -0,0 +1,36 @@
import os
FRONTEND_HOST = os.getenv('FRONTEND_HOST', 'http://localhost')
PORTAL_NAME = os.getenv('PORTAL_NAME', 'MediaCMS')
SECRET_KEY = os.getenv('SECRET_KEY', 'ma!s3^b-cw!f#7s6s0m3*jx77a@riw(7701**(r=ww%w!2+yk2')
REDIS_LOCATION = os.getenv('REDIS_LOCATION', 'redis://redis:6379/1')
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": os.getenv('POSTGRES_NAME', 'mediacms'),
"HOST": os.getenv('POSTGRES_HOST', 'db'),
"PORT": os.getenv('POSTGRES_PORT', '5432'),
"USER": os.getenv('POSTGRES_USER', 'mediacms'),
"PASSWORD": os.getenv('POSTGRES_PASSWORD', 'mediacms'),
"OPTIONS": {'pool': True},
}
}
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": REDIS_LOCATION,
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
}
}
# CELERY STUFF
BROKER_URL = REDIS_LOCATION
CELERY_RESULT_BACKEND = BROKER_URL
MP4HLS_COMMAND = "/home/mediacms.io/bento4/bin/mp4hls"
DEBUG = os.getenv('DEBUG', 'False') == 'True'

View File

@@ -23,8 +23,8 @@ http {
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
access_log /var/log/mediacms/nginx-main.access.log;
error_log /var/log/mediacms/nginx-main.error.log;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip on;
gzip_disable "msie6";

View File

@@ -2,20 +2,20 @@ server {
listen 80 ;
gzip on;
access_log /var/log/mediacms/nginx.access.log;
access_log /var/log/nginx/mediacms.io.access.log;
error_log /var/log/mediacms/nginx.error.log warn;
error_log /var/log/nginx/mediacms.io.error.log warn;
location /static {
alias /var/www/static ;
alias /home/mediacms.io/mediacms/static ;
}
location /media/original {
alias /var/www/media/original;
alias /home/mediacms.io/mediacms/media_files/original;
}
location /media {
alias /var/www/media ;
alias /home/mediacms.io/mediacms/media_files ;
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
@@ -28,7 +28,7 @@ server {
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
include /etc/nginx/uwsgi_params;
uwsgi_pass web:9000;
include /etc/nginx/sites-enabled/uwsgi_params;
uwsgi_pass 127.0.0.1:9000;
}
}

71
deploy/docker/prestart.sh Executable file
View File

@@ -0,0 +1,71 @@
#!/bin/bash
RANDOM_ADMIN_PASS=`python -c "import secrets;chars = 'abcdefghijklmnopqrstuvwxyz0123456789';print(''.join(secrets.choice(chars) for i in range(10)))"`
ADMIN_PASSWORD=${ADMIN_PASSWORD:-$RANDOM_ADMIN_PASS}
if [ X"$ENABLE_MIGRATIONS" = X"yes" ]; then
echo "Running migrations service"
python manage.py migrate
EXISTING_INSTALLATION=`echo "from users.models import User; print(User.objects.exists())" |python manage.py shell`
if [ "$EXISTING_INSTALLATION" = "True" ]; then
echo "Loaddata has already run"
else
echo "Running loaddata and creating admin user"
python manage.py loaddata fixtures/encoding_profiles.json
python manage.py loaddata fixtures/categories.json
# post_save, needs redis to succeed (ie. migrate depends on redis)
DJANGO_SUPERUSER_PASSWORD=$ADMIN_PASSWORD python manage.py createsuperuser \
--no-input \
--username=$ADMIN_USER \
--email=$ADMIN_EMAIL \
--database=default || true
echo "Created admin user with password: $ADMIN_PASSWORD"
fi
echo "RUNNING COLLECTSTATIC"
python manage.py collectstatic --noinput
# echo "Updating hostname ..."
# TODO: Get the FRONTEND_HOST from cms/local_settings.py
# echo "from django.contrib.sites.models import Site; Site.objects.update(name='$FRONTEND_HOST', domain='$FRONTEND_HOST')" | python manage.py shell
fi
# Setting up internal nginx server
# HTTPS setup is delegated to a reverse proxy running infront of the application
cp deploy/docker/nginx_http_only.conf /etc/nginx/sites-available/default
cp deploy/docker/nginx_http_only.conf /etc/nginx/sites-enabled/default
cp deploy/docker/uwsgi_params /etc/nginx/sites-enabled/uwsgi_params
cp deploy/docker/nginx.conf /etc/nginx/
#### Supervisord Configurations #####
cp deploy/docker/supervisord/supervisord-debian.conf /etc/supervisor/conf.d/supervisord-debian.conf
if [ X"$ENABLE_UWSGI" = X"yes" ] ; then
echo "Enabling uwsgi app server"
cp deploy/docker/supervisord/supervisord-uwsgi.conf /etc/supervisor/conf.d/supervisord-uwsgi.conf
fi
if [ X"$ENABLE_NGINX" = X"yes" ] ; then
echo "Enabling nginx as uwsgi app proxy and media server"
cp deploy/docker/supervisord/supervisord-nginx.conf /etc/supervisor/conf.d/supervisord-nginx.conf
fi
if [ X"$ENABLE_CELERY_BEAT" = X"yes" ] ; then
echo "Enabling celery-beat scheduling server"
cp deploy/docker/supervisord/supervisord-celery_beat.conf /etc/supervisor/conf.d/supervisord-celery_beat.conf
fi
if [ X"$ENABLE_CELERY_SHORT" = X"yes" ] ; then
echo "Enabling celery-short task worker"
cp deploy/docker/supervisord/supervisord-celery_short.conf /etc/supervisor/conf.d/supervisord-celery_short.conf
fi
if [ X"$ENABLE_CELERY_LONG" = X"yes" ] ; then
echo "Enabling celery-long task worker"
cp deploy/docker/supervisord/supervisord-celery_long.conf /etc/supervisor/conf.d/supervisord-celery_long.conf
rm /var/run/mediacms/* -f # remove any stale id, so that on forced restarts of celery workers there are no stale processes that prevent new ones
fi

View File

@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIICwzCCAaugAwIBAgIJAOyvdwguJQd+MA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV
BAMTCWxvY2FsaG9zdDAeFw0yMTAxMjQxMjUwMzFaFw0zMTAxMjIxMjUwMzFaMBQx
EjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAONswEwBzkgoO+lkewiKUnwvYqC54qleCUg9hidqjoyzd5XWKh1mIF7aaSCG
rJGSxCce8CbqAqGkpvsgXzwwbY72l7FwmAXFHO5ObQfpmFhjt2fsKRM9MTCo/UyU
liuhgP+Q+BNzUontTUC40NVHs8R7IHG4z8unB7qB/7zGK2tfilLB8JDqPTkc22vN
C4P1YxiGyY5bm37wQrroC9zPJ8bqanrF9Y90QJHubibnPWqnZvK2HkDWjp5LYkn8
IuzBycs1cLd8eMjU9aT72kweykvnGDDc3YbXFzT2zBTGSFEBROsVdPrNF9PaeE3j
pu4UZ8Ge3Fp3VYd+04DnWtbQq0MCAwEAAaMYMBYwFAYDVR0RBA0wC4IJbG9jYWxo
b3N0MA0GCSqGSIb3DQEBBQUAA4IBAQAdm2aGn4evosbdWgBHgzr6oYWBIiPpf1SA
GXizuf5OaMActFP0rZ0mogndLH5d51J2qqSfOtaWSA5qwlPvDSTn1nvJeHoVLfZf
kQHaB7/DaOPGsZCQBELPhYHwl7+Ej3HYE+siiaRfjC2NVgf8P/pAsTlKbe2e+34l
GwWSFol24w5xAmUezCF41JiZbqHoZhSh7s/PuJnK2RvhpjkrIot8GvxnbvOcKDIv
JzEKo3qPq8pc5RBkpP7Kp2+EgAYn1xAn0CekxZracW/MY+tg2mCeFucZW2V1iwVs
LpAw6GJnjYz5mbrQskPbrJ9t78JGUKQ0kL/VUTfryUHMHYCiJlvd
-----END CERTIFICATE-----

View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA42zATAHOSCg76WR7CIpSfC9ioLniqV4JSD2GJ2qOjLN3ldYq
HWYgXtppIIaskZLEJx7wJuoCoaSm+yBfPDBtjvaXsXCYBcUc7k5tB+mYWGO3Z+wp
Ez0xMKj9TJSWK6GA/5D4E3NSie1NQLjQ1UezxHsgcbjPy6cHuoH/vMYra1+KUsHw
kOo9ORzba80Lg/VjGIbJjlubfvBCuugL3M8nxupqesX1j3RAke5uJuc9aqdm8rYe
QNaOnktiSfwi7MHJyzVwt3x4yNT1pPvaTB7KS+cYMNzdhtcXNPbMFMZIUQFE6xV0
+s0X09p4TeOm7hRnwZ7cWndVh37TgOda1tCrQwIDAQABAoIBAQCmKKyOW7tlCNBN
AzbI1JbTWKOMnoM2DxhlCV5cqgOgVPcIKEL428bGxniMZRjr+vkJRBddtxdZFj1R
uSMbjJ5fF1dZMtQ/UvaCPhZ283p1CdXUPbz863ZnAPCf5Oea1RK0piw5ucYSM6h/
owgg65Qx92uK6uYW+uAwqg440+ihNvnaZoVTx5CjZbL9KISkrlNJnuYiB5vzOD0i
UVklO5Qz8VCuOcOVGZCA2SxHm4HAbg/aiQnpaUa9de4TsZ4ygF66pZh77T0wNOos
sS1riKtHQpX+osJyoTI/rIKFAhycsZ+AA7Qpu6GW4xQlNS6K8vRiIbktwkC+IT0O
RSn8Dg7BAoGBAPe5R8SpgXx9jKdA1eFa/Vjx5bmB96r2MviIOIWF8rs2K33xe+rj
v+BZ2ZjdpVjcm2nRMf9r/eDq2ScNFWmKoZsUmdyT84Qq9yLcTSUdno+zCy+L0LNH
DqJq5jIxJaV7amHeR/w10BVuiDmzhSsTmhfnXTUGRO/h2PjRyC3yEYdxAoGBAOsF
2+gTsdOGlq6AVzW5MLZkreq8WCU2wWpZRiCPh6HJa8htuynYxO5AWUiNUbYKddj2
0za9DFiXgH+Oo8wrkTYLEdN0T5/o+ScL5t3VG3m9R6pnuudLC2vmGQP0hNuZUpnF
7FzdJ85h6taR2bM1zFzOfl81K0BhTHGxTU2r70vzAoGAVXuLJ3LyqtnMKn72DzDN
0d6PTkdqBoW0qwyerHy/eRjFQ02MXE7BDJMUwmphv1tJCefVX/WNAwsnahFavTPI
dnJSccpgMtB8vXvV5yPkbmPzTTHrD6JKi4Nl8hYBjqwa1rDUmFSdfHfK7FZlcqrt
9qexAzYpnbmKnLoPYMNyhxECgYEAm5OCUeuPoL2MS7GLiXWwyFx3QFczZlcLzBGS
uYUpvLBwF/qDlhz3p9uS/tMFzyK3hktF4Ate+9o2ZroOtd31PzgusbJh7zIylGVt
i1VB3eGtaiFGeUuVIPTthE++Dvw80KxTXdnMOvNYmHduDBLF2H2c6/tvSSvfhbdf
u9XgD38CgYAiLcVySxMKNpsXatuC31wjT+rnaH22SD/7pXe2q6MRW/s+bGOspu0v
NeJSLoM98v8F99q0W0lgqesYJVI20Frru0DfXIp60ryaDolzve3Iwk8SOJUlcnUG
cCtmPUkjyr18QAlrcCB4PozJGjpPWyabaY8gGwo8wAEpJWHrIJlHew==
-----END RSA PRIVATE KEY-----

17
deploy/docker/start.sh Executable file
View File

@@ -0,0 +1,17 @@
#! /usr/bin/env sh
set -e
# If there's a prestart.sh script in the /app directory, run it before starting
PRE_START_PATH=deploy/docker/prestart.sh
echo "Checking for script in $PRE_START_PATH"
if [ -f $PRE_START_PATH ] ; then
echo "Running script $PRE_START_PATH"
. $PRE_START_PATH
else
echo "There is no script $PRE_START_PATH"
fi
# Start Supervisor, with Nginx and uWSGI
echo "Starting server using supervisord..."
exec /usr/bin/supervisord

View File

@@ -0,0 +1,12 @@
[program:celery_beat]
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
startsecs=0
numprocs=1
user=www-data
directory=/home/mediacms.io/mediacms
priority=300
startinorder=true
command=/home/mediacms.io/bin/celery beat --pidfile=/var/run/mediacms/beat%%n.pid --loglevel=INFO --logfile=/home/mediacms.io/mediacms/logs/celery_beat.log

View File

@@ -0,0 +1,13 @@
[program:celery_long]
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
startsecs=10
numprocs=1
user=www-data
directory=/home/mediacms.io/mediacms
priority=500
startinorder=true
startsecs=0
command=/home/mediacms.io/bin/celery multi start long1 --pidfile=/var/run/mediacms/%%n.pid --loglevel=INFO --logfile=/home/mediacms.io/mediacms/logs/celery_long.log -Ofair --prefetch-multiplier=1 -Q long_tasks

View File

@@ -0,0 +1,12 @@
[program:celery_short]
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
startsecs=0
numprocs=1
user=www-data
directory=/home/mediacms.io/mediacms
priority=400
startinorder=true
command=/home/mediacms.io/bin/celery multi start short1 short2 --pidfile=/var/run/mediacms/%%n.pid --loglevel=INFO --logfile=/home/mediacms.io/mediacms/logs/celery_short.log --soft-time-limit=300 -c10 -Q short_tasks

View File

@@ -0,0 +1,2 @@
[supervisord]
nodaemon=true

View File

@@ -0,0 +1,11 @@
[program:nginx]
command=/usr/sbin/nginx -g 'daemon off;'
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
priority=200
startinorder=true
startsecs=0
# Graceful stop, see http://nginx.org/en/docs/control.html
stopsignal=QUIT

View File

@@ -0,0 +1,9 @@
[program:uwsgi]
command=/home/mediacms.io/bin/uwsgi --ini /home/mediacms.io/mediacms/deploy/docker/uwsgi.ini
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
priority=100
startinorder=true
startsecs=0

View File

@@ -1,161 +0,0 @@
version: "3.8"
# Production setup with automatic HTTPS via Let's Encrypt
# Uses https://github.com/nginx-proxy/acme-companion
services:
nginx-proxy:
image: nginxproxy/nginx-proxy
container_name: nginx-proxy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- conf:/etc/nginx/conf.d
- vhost:/etc/nginx/vhost.d
- html:/usr/share/nginx/html
- dhparam:/etc/nginx/dhparam
- certs:/etc/nginx/certs:ro
- /var/run/docker.sock:/tmp/docker.sock:ro
- ./config/nginx-proxy/client_max_body_size.conf:/etc/nginx/conf.d/client_max_body_size.conf:ro
acme-companion:
image: nginxproxy/acme-companion
container_name: nginx-proxy-acme
restart: unless-stopped
volumes_from:
- nginx-proxy
volumes:
- certs:/etc/nginx/certs:rw
- acme:/etc/acme.sh
- /var/run/docker.sock:/var/run/docker.sock:ro
migrations:
image: mediacms/mediacms:7.3
command: ["/bin/bash", "/home/mediacms.io/mediacms/scripts/run-migrations.sh"]
environment:
ADMIN_USER: 'admin'
ADMIN_EMAIL: 'admin@localhost'
# ADMIN_PASSWORD: 'uncomment_and_set_password_here'
restart: "no"
depends_on:
redis:
condition: service_healthy
db:
condition: service_healthy
volumes:
- static_files:/home/mediacms.io/mediacms/static_files
- media_files:/home/mediacms.io/mediacms/media_files
- logs:/home/mediacms.io/mediacms/logs
web:
image: mediacms/mediacms:7.3
restart: unless-stopped
expose:
- "9000"
depends_on:
migrations:
condition: service_completed_successfully
redis:
condition: service_healthy
db:
condition: service_healthy
volumes:
- static_files:/home/mediacms.io/mediacms/static_files
- media_files:/home/mediacms.io/mediacms/media_files
- logs:/home/mediacms.io/mediacms/logs
nginx:
image: mediacms/mediacms-nginx:7.3
restart: unless-stopped
expose:
- "80"
environment:
# These are required for nginx-proxy to route traffic correctly
VIRTUAL_HOST: 'mediacms.example.com' # CHANGE THIS to your domain
LETSENCRYPT_HOST: 'mediacms.example.com' # CHANGE THIS to your domain
LETSENCRYPT_EMAIL: 'admin@example.com' # CHANGE THIS to your email
depends_on:
- web
volumes:
- static_files:/var/www/static:ro
- media_files:/var/www/media:ro
- logs:/var/log/mediacms
celery_beat:
image: mediacms/mediacms-worker:7.3
restart: unless-stopped
command: ["/home/mediacms.io/bin/celery", "-A", "cms", "beat", "--loglevel=INFO"]
depends_on:
migrations:
condition: service_completed_successfully
redis:
condition: service_healthy
volumes:
- media_files:/home/mediacms.io/mediacms/media_files
- logs:/home/mediacms.io/mediacms/logs
celery_short:
image: mediacms/mediacms-worker:7.3
restart: unless-stopped
command: ["/home/mediacms.io/bin/celery", "-A", "cms", "worker", "-Q", "short_tasks", "-c", "10", "--soft-time-limit=300", "--loglevel=INFO", "-n", "short@%h"]
depends_on:
migrations:
condition: service_completed_successfully
redis:
condition: service_healthy
volumes:
- media_files:/home/mediacms.io/mediacms/media_files
- logs:/home/mediacms.io/mediacms/logs
celery_long:
image: mediacms/mediacms-worker:7.3
# To use extra codecs, change image to: mediacms/mediacms-worker:7.3-full
restart: unless-stopped
command: ["/home/mediacms.io/bin/celery", "-A", "cms", "worker", "-Q", "long_tasks", "-c", "1", "-Ofair", "--prefetch-multiplier=1", "--loglevel=INFO", "-n", "long@%h"]
depends_on:
migrations:
condition: service_completed_successfully
redis:
condition: service_healthy
volumes:
- media_files:/home/mediacms.io/mediacms/media_files
- logs:/home/mediacms.io/mediacms/logs
db:
image: postgres:17.2-alpine
restart: unless-stopped
environment:
POSTGRES_USER: mediacms
POSTGRES_PASSWORD: mediacms
POSTGRES_DB: mediacms
TZ: Europe/London
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:alpine
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
volumes:
conf:
vhost:
html:
dhparam:
certs:
acme:
postgres_data:
static_files:
media_files:
logs:

View File

@@ -1,7 +1,4 @@
version: "3.8"
# Development setup with hot-reload and file mounts
# This is the ONLY compose file that mounts the source code
version: "3"
services:
migrations:
@@ -11,126 +8,82 @@ services:
target: base
args:
- DEVELOPMENT_MODE=True
image: mediacms/mediacms-dev:7.3
command: ["/bin/bash", "/home/mediacms.io/mediacms/scripts/run-migrations.sh"]
image: mediacms/mediacms-dev:latest
volumes:
- ./:/home/mediacms.io/mediacms/
command: "./deploy/docker/prestart.sh"
environment:
DEVELOPMENT_MODE: 'True'
DEBUG: 'True'
DEVELOPMENT_MODE: True
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_CELERY_BEAT: 'no'
ADMIN_USER: 'admin'
ADMIN_EMAIL: 'admin@localhost'
ADMIN_PASSWORD: 'admin'
restart: "no"
restart: on-failure
depends_on:
redis:
condition: service_healthy
db:
condition: service_healthy
volumes:
- ./:/home/mediacms.io/mediacms/
web:
image: mediacms/mediacms-dev:7.3
restart: unless-stopped
ports:
- "80:8000"
command: ["python", "manage.py", "runserver", "0.0.0.0:8000"]
environment:
DEVELOPMENT_MODE: 'True'
DEBUG: 'True'
depends_on:
migrations:
condition: service_completed_successfully
redis:
condition: service_healthy
db:
condition: service_healthy
volumes:
- ./:/home/mediacms.io/mediacms/
frontend:
image: node:20-alpine
image: node:20
volumes:
- ${PWD}/frontend:/home/mediacms.io/mediacms/frontend/
working_dir: /home/mediacms.io/mediacms/frontend/
command: sh -c "npm install && npm run start"
command: bash -c "npm install && npm run start"
env_file:
- ${PWD}/frontend/.env
ports:
- "8088:8088"
environment:
- NODE_ENV=development
env_file:
- ./frontend/.env
volumes:
- ./frontend:/home/mediacms.io/mediacms/frontend/
depends_on:
- web
celery_beat:
image: mediacms/mediacms-dev:7.3
restart: unless-stopped
command: ["/home/mediacms.io/bin/celery", "-A", "cms", "beat", "--loglevel=INFO"]
web:
image: mediacms/mediacms-dev:latest
command: "python manage.py runserver 0.0.0.0:80"
environment:
DEVELOPMENT_MODE: 'True'
DEBUG: 'True'
depends_on:
migrations:
condition: service_completed_successfully
redis:
condition: service_healthy
DEVELOPMENT_MODE: True
ports:
- "80:80"
volumes:
- ./:/home/mediacms.io/mediacms/
celery_short:
image: mediacms/mediacms-dev:7.3
restart: unless-stopped
command: ["/home/mediacms.io/bin/celery", "-A", "cms", "worker", "-Q", "short_tasks", "-c", "10", "--soft-time-limit=300", "--loglevel=INFO", "-n", "short@%h"]
environment:
DEVELOPMENT_MODE: 'True'
DEBUG: 'True'
depends_on:
migrations:
condition: service_completed_successfully
redis:
condition: service_healthy
volumes:
- ./:/home/mediacms.io/mediacms/
celery_long:
image: mediacms/mediacms-dev:7.3
restart: unless-stopped
command: ["/home/mediacms.io/bin/celery", "-A", "cms", "worker", "-Q", "long_tasks", "-c", "1", "-Ofair", "--prefetch-multiplier=1", "--loglevel=INFO", "-n", "long@%h"]
environment:
DEVELOPMENT_MODE: 'True'
DEBUG: 'True'
depends_on:
migrations:
condition: service_completed_successfully
redis:
condition: service_healthy
volumes:
- ./:/home/mediacms.io/mediacms/
- migrations
db:
image: postgres:17.2-alpine
restart: unless-stopped
volumes:
- ../postgres_data:/var/lib/postgresql/data/
restart: always
environment:
POSTGRES_USER: mediacms
POSTGRES_PASSWORD: mediacms
POSTGRES_DB: mediacms
TZ: Europe/London
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:alpine
restart: unless-stopped
image: "redis:alpine"
restart: always
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
interval: 30s
timeout: 10s
retries: 3
volumes:
postgres_data:
celery_worker:
image: mediacms/mediacms-dev:latest
deploy:
replicas: 1
volumes:
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_BEAT: 'no'
ENABLE_MIGRATIONS: 'no'
depends_on:
- web

View File

@@ -1,120 +1,86 @@
version: "3.8"
version: "3"
services:
migrations:
image: mediacms/mediacms:7.3
command: ["/bin/bash", "/home/mediacms.io/mediacms/scripts/run-migrations.sh"]
image: mediacms/mediacms:latest
volumes:
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_CELERY_BEAT: 'no'
ADMIN_USER: 'admin'
ADMIN_EMAIL: 'admin@localhost'
# ADMIN_PASSWORD: 'uncomment_and_set_password_here'
restart: "no"
command: "./deploy/docker/prestart.sh"
restart: on-failure
depends_on:
redis:
condition: service_healthy
db:
condition: service_healthy
volumes:
- static_files:/home/mediacms.io/mediacms/static_files
- media_files:/home/mediacms.io/mediacms/media_files
- logs:/home/mediacms.io/mediacms/logs
web:
image: mediacms/mediacms:7.3
restart: unless-stopped
expose:
- "9000"
depends_on:
migrations:
condition: service_completed_successfully
redis:
condition: service_healthy
db:
condition: service_healthy
volumes:
- static_files:/home/mediacms.io/mediacms/static_files
- media_files:/home/mediacms.io/mediacms/media_files
- logs:/home/mediacms.io/mediacms/logs
nginx:
image: mediacms/mediacms-nginx:7.3
restart: unless-stopped
image: mediacms/mediacms:latest
deploy:
replicas: 1
ports:
- "80:80"
depends_on:
- web
volumes:
- static_files:/var/www/static:ro
- media_files:/var/www/media:ro
- logs:/var/log/mediacms
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_CELERY_BEAT: 'no'
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_MIGRATIONS: 'no'
depends_on:
- migrations
celery_beat:
image: mediacms/mediacms-worker:7.3
restart: unless-stopped
command: ["/home/mediacms.io/bin/celery", "-A", "cms", "beat", "--loglevel=INFO"]
depends_on:
migrations:
condition: service_completed_successfully
redis:
condition: service_healthy
image: mediacms/mediacms:latest
volumes:
- media_files:/home/mediacms.io/mediacms/media_files
- logs:/home/mediacms.io/mediacms/logs
celery_short:
image: mediacms/mediacms-worker:7.3
restart: unless-stopped
command: ["/home/mediacms.io/bin/celery", "-A", "cms", "worker", "-Q", "short_tasks", "-c", "10", "--soft-time-limit=300", "--loglevel=INFO", "-n", "short@%h"]
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_MIGRATIONS: 'no'
depends_on:
migrations:
condition: service_completed_successfully
redis:
condition: service_healthy
- redis
celery_worker:
image: mediacms/mediacms:latest
deploy:
replicas: 1
volumes:
- media_files:/home/mediacms.io/mediacms/media_files
- logs:/home/mediacms.io/mediacms/logs
celery_long:
image: mediacms/mediacms-worker:7.3
# To use extra codecs, change image to: mediacms/mediacms-worker:7.3-full
restart: unless-stopped
command: ["/home/mediacms.io/bin/celery", "-A", "cms", "worker", "-Q", "long_tasks", "-c", "1", "-Ofair", "--prefetch-multiplier=1", "--loglevel=INFO", "-n", "long@%h"]
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_BEAT: 'no'
ENABLE_MIGRATIONS: 'no'
depends_on:
migrations:
condition: service_completed_successfully
redis:
condition: service_healthy
volumes:
- media_files:/home/mediacms.io/mediacms/media_files
- logs:/home/mediacms.io/mediacms/logs
- migrations
db:
image: postgres:17.2-alpine
restart: unless-stopped
volumes:
- ../postgres_data:/var/lib/postgresql/data/
restart: always
environment:
POSTGRES_USER: mediacms
POSTGRES_PASSWORD: mediacms
POSTGRES_DB: mediacms
TZ: Europe/London
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:alpine
restart: unless-stopped
image: "redis:alpine"
restart: always
healthcheck:
test: ["CMD", "redis-cli", "ping"]
test: ["CMD", "redis-cli","ping"]
interval: 10s
timeout: 5s
retries: 3
volumes:
postgres_data:
static_files:
media_files:
logs:

View File

@@ -1,367 +0,0 @@
# MediaCMS 7.3 Docker Architecture Migration Guide
## Overview
MediaCMS 7.3 introduces a modernized Docker architecture that removes supervisord and implements Docker best practices with one process per container.
## What Changed
### Old Architecture (pre-7.3)
- Single multi-purpose image with supervisord
- Environment variables (`ENABLE_UWSGI`, `ENABLE_NGINX`, etc.) to control services
- All services bundled in `deploy/docker/` folder
- File mounts required for all deployments
### New Architecture (7.3+)
- **Dedicated images** for each service:
- `mediacms/mediacms:7.3` - Django/uWSGI application
- `mediacms/mediacms-worker:7.3` - Celery workers
- `mediacms/mediacms-worker:7.3-full` - Celery workers with extra codecs
- `mediacms/mediacms-nginx:7.3` - Nginx web server
- **No supervisord** - Native Docker process management
- **Separated services**:
- `migrations` - Runs database migrations on every startup
- `nginx` - Serves static/media files and proxies to Django
- `web` - Django application (uWSGI)
- `celery_short` - Short-running tasks (thumbnails, etc.)
- `celery_long` - Long-running tasks (video encoding)
- `celery_beat` - Task scheduler
- **No ENABLE_* environment variables**
- **Config centralized** in `config/` directory
- **File mounts only for development** (`docker-compose-dev.yaml`)
## Directory Structure
```
config/
├── nginx/
│ ├── nginx.conf # Main nginx config
│ ├── site.conf # Virtual host config
│ └── uwsgi_params # uWSGI parameters
├── nginx-proxy/
│ └── client_max_body_size.conf # For production HTTPS proxy
├── uwsgi/
│ └── uwsgi.ini # uWSGI configuration
└── imagemagick/
└── policy.xml # ImageMagick policy
scripts/
├── entrypoint-web.sh # Web container entrypoint
├── entrypoint-worker.sh # Worker container entrypoint
└── run-migrations.sh # Migration script
Dockerfile.new # Main Dockerfile (base, web, worker, worker-full)
Dockerfile.nginx # Nginx Dockerfile
docker-compose.yaml # Production deployment
docker-compose-cert.yaml # Production with HTTPS
docker-compose-dev.yaml # Development with file mounts
```
## Migration Steps
### For Existing Production Systems
#### Step 1: Backup your data
```bash
# Backup database
docker exec mediacms_db_1 pg_dump -U mediacms mediacms > backup.sql
# Backup media files
cp -r media_files media_files.backup
```
#### Step 2: Update configuration location
```bash
# The client_max_body_size.conf has moved
# No action needed if you haven't customized it
```
#### Step 3: Pull latest images
```bash
docker pull mediacms/mediacms:7.3
docker pull mediacms/mediacms-worker:7.3
docker pull mediacms/mediacms-nginx:7.3
```
#### Step 4: Update docker-compose file
If using **docker-compose.yaml**:
- No changes needed, just use the new version
If using **docker-compose-cert.yaml** (HTTPS):
- Update `VIRTUAL_HOST`, `LETSENCRYPT_HOST`, and `LETSENCRYPT_EMAIL` in the nginx service
- Update the path to client_max_body_size.conf:
```yaml
- ./config/nginx-proxy/client_max_body_size.conf:/etc/nginx/conf.d/client_max_body_size.conf:ro
```
#### Step 5: Restart services
```bash
docker compose down
docker compose up -d
```
### For Development Systems
Development now requires the `-dev` compose file:
```bash
# Old way (no longer works)
docker compose up
# New way (development)
docker compose -f docker-compose-dev.yaml up
```
## Deployment Options
### Standard Deployment (HTTP)
**File**: `docker-compose.yaml`
**Command**:
```bash
docker compose up -d
```
**Features**:
- Self-contained images (no file mounts)
- Nginx serves on port 80
- Separate containers for each service
- Named volumes for persistence
**Architecture**:
```
Client → nginx:80 → web:9000 (uWSGI)
static_files (volume)
media_files (volume)
```
### Production Deployment (HTTPS with Let's Encrypt)
**File**: `docker-compose-cert.yaml`
**Prerequisites**:
1. Domain name pointing to your server
2. Ports 80 and 443 open
**Setup**:
```bash
# 1. Edit docker-compose-cert.yaml
# Update these values in the nginx service:
# VIRTUAL_HOST: 'your-domain.com'
# LETSENCRYPT_HOST: 'your-domain.com'
# LETSENCRYPT_EMAIL: 'your-email@example.com'
# 2. Start services
docker compose -f docker-compose-cert.yaml up -d
# 3. Check logs
docker compose -f docker-compose-cert.yaml logs -f nginx-proxy acme-companion
```
**Features**:
- Automatic HTTPS via Let's Encrypt
- Certificate auto-renewal
- Reverse proxy handles SSL termination
**Architecture**:
```
Client → nginx-proxy:443 (HTTPS) → nginx:80 → web:9000 (uWSGI)
```
### Development Deployment
**File**: `docker-compose-dev.yaml`
**Command**:
```bash
docker compose -f docker-compose-dev.yaml up
```
**Features**:
- Source code mounted for live editing
- Django debug mode enabled
- Django's `runserver` instead of uWSGI
- Frontend hot-reload on port 8088
- No nginx (direct Django access on port 80)
**Ports**:
- `80` - Django API
- `8088` - Frontend dev server
## Configuration
### Environment Variables
All configuration is done via environment variables or `cms/local_settings.py`.
**Key Variables**:
- `FRONTEND_HOST` - Your domain (e.g., `https://mediacms.example.com`)
- `PORTAL_NAME` - Your portal name
- `SECRET_KEY` - Django secret key
- `POSTGRES_*` - Database credentials
- `REDIS_LOCATION` - Redis connection string
- `DEBUG` - Enable debug mode (development only)
**Setting variables**:
Option 1: In docker-compose file:
```yaml
environment:
FRONTEND_HOST: 'https://mediacms.example.com'
PORTAL_NAME: 'My MediaCMS'
```
Option 2: Using .env file (recommended):
```bash
# Create .env file
cat > .env << EOF
FRONTEND_HOST=https://mediacms.example.com
PORTAL_NAME=My MediaCMS
SECRET_KEY=your-secret-key-here
EOF
```
### Customizing Settings
For advanced customization, you can build a custom image:
```dockerfile
# Dockerfile.custom
FROM mediacms/mediacms:7.3
COPY my_local_settings.py /home/mediacms.io/mediacms/cms/local_settings.py
```
## Celery Workers
### Standard Workers
By default, `celery_long` uses the standard image:
```yaml
celery_long:
image: mediacms/mediacms-worker:7.3
```
### Full Workers (Extra Codecs)
To enable extra codecs for better transcoding (including Whisper for subtitles):
**Edit docker-compose file**:
```yaml
celery_long:
image: mediacms/mediacms-worker:7.3-full # Changed from :7.3
```
**Then restart**:
```bash
docker compose up -d celery_long
```
### Scaling Workers
You can scale workers independently:
```bash
# Scale short task workers
docker compose up -d --scale celery_short=3
# Scale long task workers
docker compose up -d --scale celery_long=2
```
## Troubleshooting
### Migrations not running
```bash
# Check migrations container logs
docker compose logs migrations
# Manually run migrations
docker compose run --rm migrations
```
### Static files not loading
```bash
# Ensure migrations completed (it runs collectstatic)
docker compose logs migrations
# Check nginx can access volumes
docker compose exec nginx ls -la /var/www/static
```
### Permission issues
```bash
# Check volume ownership
docker compose exec web ls -la /home/mediacms.io/mediacms/media_files
# If needed, rebuild images
docker compose build --no-cache
```
### Celery workers not processing tasks
```bash
# Check worker logs
docker compose logs celery_short celery_long
# Check Redis connection
docker compose exec redis redis-cli ping
# Restart workers
docker compose restart celery_short celery_long celery_beat
```
## Removed Components
The following are **no longer used** in 7.3:
- ❌ `deploy/docker/supervisord/` - Supervisord configs
- ❌ `deploy/docker/start.sh` - Start script
- ❌ `deploy/docker/entrypoint.sh` - Old entrypoint
- ❌ Environment variables: `ENABLE_UWSGI`, `ENABLE_NGINX`, `ENABLE_CELERY_BEAT`, `ENABLE_CELERY_SHORT`, `ENABLE_CELERY_LONG`, `ENABLE_MIGRATIONS`
**These are still available but moved**:
- ✅ `config/nginx/` - Nginx configs (moved from `deploy/docker/`)
- ✅ `config/uwsgi/` - uWSGI config (moved from `deploy/docker/`)
- ✅ `config/nginx-proxy/` - Reverse proxy config (moved from `deploy/docker/reverse_proxy/`)
## Persistent Volumes
MediaCMS 7.3 uses Docker named volumes for data persistence:
- **`media_files`** - All uploaded media (videos, images, thumbnails, HLS streams)
- Mounted on: migrations, web, nginx, celery_beat, celery_short, celery_long
- Persists across container restarts, updates, and image removals
- **`logs`** - Application and nginx logs
- Mounted on: migrations, web, nginx, celery_beat, celery_short, celery_long
- Nginx logs: `/var/log/mediacms/nginx.access.log`, `/var/log/mediacms/nginx.error.log`
- Django/Celery logs: `/home/mediacms.io/mediacms/logs/`
- Persists across container restarts, updates, and image removals
- **`static_files`** - Django static files (CSS, JS, images)
- Mounted on: migrations, web, nginx
- Regenerated during migrations via `collectstatic`
- **`postgres_data`** - PostgreSQL database
- Mounted on: db
- Persists across container restarts, updates, and image removals
**Important**: Use `docker compose down -v` to remove volumes (⚠️ causes data loss!)
## Benefits of New Architecture
1. **Better resource management** - Scale services independently
2. **Easier debugging** - Clear separation of concerns
3. **Faster restarts** - Restart only affected services
4. **Production-ready** - No file mounts, immutable images
5. **Standard Docker practices** - One process per container
6. **Clearer logs** - Each service has isolated logs, persistent storage
7. **Better health checks** - Per-service monitoring
8. **Data persistence** - media_files and logs survive all container operations
## Support
For issues or questions:
- GitHub Issues: https://github.com/mediacms-io/mediacms/issues
- Documentation: https://docs.mediacms.io

View File

@@ -164,123 +164,53 @@ Database is stored on ../postgres_data/ and media_files on media_files/
## 4. Docker Deployment options
**⚠️ IMPORTANT**: MediaCMS 7.3 introduces a new Docker architecture. If you're upgrading from an earlier version, please see the [Migration Guide](DOCKER_V7.3_MIGRATION.md).
The mediacms image is built to use supervisord as the main process, which manages one or more services required to run mediacms. We can toggle which services are run in a given container by setting the environment variables below to `yes` or `no`:
### Architecture Overview
* ENABLE_UWSGI
* ENABLE_NGINX
* ENABLE_CELERY_BEAT
* ENABLE_CELERY_SHORT
* ENABLE_CELERY_LONG
* ENABLE_MIGRATIONS
MediaCMS 7.3+ uses a modern microservices architecture with dedicated containers:
By default, all these services are enabled, but in order to create a scaleable deployment, some of them can be disabled, splitting the service up into smaller services.
- **nginx** - Web server for static/media files and reverse proxy
- **web** - Django application (uWSGI)
- **celery_short** - Short-running background tasks
- **celery_long** - Long-running tasks (video encoding)
- **celery_beat** - Task scheduler
- **migrations** - Database migrations (runs on startup)
- **db** - PostgreSQL database
- **redis** - Cache and message broker
Also see the `Dockerfile` for other environment variables which you may wish to override. Application settings, eg. `FRONTEND_HOST` can also be overridden by updating the `deploy/docker/local_settings.py` file.
### Key Changes from Previous Versions
To run, update the configs above if necessary, build the image by running `docker compose build`, then run `docker compose run`
-**No supervisord** - Native Docker process management
-**Dedicated images** per service
-**No ENABLE_* environment variables** - Services are separated into individual containers
-**Production images** don't mount source code (immutable)
-**config/** directory for centralized configuration
-**Separate celery workers** for short and long tasks
### Simple Deployment, accessed as http://localhost
### Configuration
The main container runs migrations, mediacms_web, celery_beat, celery_workers (celery_short and celery_long services), exposed on port 80 supported by redis and postgres database.
Application settings can be overridden using environment variables in your docker-compose file or by building a custom image with a modified `cms/local_settings.py` file.
The FRONTEND_HOST in `deploy/docker/local_settings.py` is configured as http://localhost, on the docker host machine.
Key environment variables:
- `FRONTEND_HOST` - Your domain (e.g., `https://mediacms.example.com`)
- `PORTAL_NAME` - Portal name
- `SECRET_KEY` - Django secret key
- `DEBUG` - Enable debug mode (development only)
- Database and Redis connection settings
### Server with ssl certificate through letsencrypt service, accessed as https://my_domain.com
Before trying this out make sure the ip points to my_domain.com.
See the [Migration Guide](DOCKER_V7.3_MIGRATION.md) for detailed configuration options
With this method [this deployment](../docker-compose-letsencrypt.yaml) is used.
### Simple Deployment (HTTP)
Edit this file and set `VIRTUAL_HOST` as my_domain.com, `LETSENCRYPT_HOST` as my_domain.com, and your email on `LETSENCRYPT_EMAIL`
Use `docker-compose.yaml` for a standard HTTP deployment on port 80:
Edit `deploy/docker/local_settings.py` and set https://my_domain.com as `FRONTEND_HOST`
```bash
docker compose up -d
```
Now run `docker compose -f docker-compose-letsencrypt.yaml up`, when installation finishes you will be able to access https://my_domain.com using a valid Letsencrypt certificate!
This starts all services (nginx, web, celery workers, database, redis) with the nginx container exposed on port 80. Access at http://localhost or http://your-server-ip.
### Advanced Deployment, accessed as http://localhost:8000
**Features:**
- Production-ready with immutable images
- Named volumes for data persistence
- Separate containers for each service
Here we can run 1 mediacms_web instance, with the FRONTEND_HOST in `deploy/docker/local_settings.py` configured as http://localhost:8000. This is bootstrapped by a single migrations instance and supported by a single celery_beat instance and 1 or more celery_worker instances. Redis and postgres containers are also used for persistence. Clients can access the service on http://localhost:8000, on the docker host machine. This is similar to [this deployment](../docker-compose.yaml), with a `port` defined in FRONTEND_HOST.
### Production Deployment with HTTPS (Let's Encrypt)
### Advanced Deployment, with reverse proxy, accessed as http://mediacms.io
Use `docker-compose-cert.yaml` for automatic HTTPS with Let's Encrypt:
Here we can use `jwilder/nginx-proxy` to reverse proxy to 1 or more instances of mediacms_web supported by other services as mentioned in the previous deployment. The FRONTEND_HOST in `deploy/docker/local_settings.py` is configured as http://mediacms.io, nginx-proxy has port 80 exposed. Clients can access the service on http://mediacms.io (Assuming DNS or the hosts file is setup correctly to point to the IP of the nginx-proxy instance). This is similar to [this deployment](../docker-compose-http-proxy.yaml).
**Prerequisites:**
- Domain name pointing to your server
- Ports 80 and 443 open
### Advanced Deployment, with reverse proxy, accessed as https://localhost
**Setup:**
1. Edit `docker-compose-cert.yaml` and update:
- `VIRTUAL_HOST` - Your domain
- `LETSENCRYPT_HOST` - Your domain
- `LETSENCRYPT_EMAIL` - Your email
2. Run:
```bash
docker compose -f docker-compose-cert.yaml up -d
```
This uses `nginxproxy/nginx-proxy` with `acme-companion` for automatic HTTPS certificate management. Access at https://your-domain.com.
### Development Deployment
Use `docker-compose-dev.yaml` for development with live code reloading:
```bash
docker compose -f docker-compose-dev.yaml up
```
**Features:**
- Source code mounted for live editing
- Django debug mode enabled
- Frontend dev server on port 8088
- Direct Django access (no nginx) on port 80
### Scaling Workers
Scale celery workers independently based on load:
```bash
# Scale short task workers to 3 instances
docker compose up -d --scale celery_short=3
# Scale long task workers to 2 instances
docker compose up -d --scale celery_long=2
```
### Using Extra Codecs (Full Image)
For advanced transcoding features (including Whisper for automatic subtitles), use the full worker image:
Edit your docker-compose file:
```yaml
celery_long:
image: mediacms/mediacms-worker:7.3-full # Changed from :7.3
```
Then restart:
```bash
docker compose up -d celery_long
```
The reverse proxy (`jwilder/nginx-proxy`) can be configured to provide SSL termination using self-signed certificates, letsencrypt or CA signed certificates (see: https://hub.docker.com/r/jwilder/nginx-proxy or [LetsEncrypt Example](https://www.singularaspect.com/use-nginx-proxy-and-letsencrypt-companion-to-host-multiple-websites/) ). In this case the FRONTEND_HOST should be set to https://mediacms.io. This is similar to [this deployment](../docker-compose-http-proxy.yaml).
### A Scaleable Deployment Architecture (Docker, Swarm, Kubernetes)
The architecture below provides a conceptual design for deployments based on kubernetes and docker swarm. It allows for horizontal scaleability through the use of multiple web instances and celery workers. For large deployments, managed postgres, redis and storage may be adopted.
The architecture below generalises all the deployment scenarios above, and provides a conceptual design for other deployments based on kubernetes and docker swarm. It allows for horizontal scaleability through the use of multiple mediacms_web instances and celery_workers. For large deployments, managed postgres, redis and storage may be adopted.
![MediaCMS](images/architecture.png)
@@ -288,36 +218,24 @@ The architecture below provides a conceptual design for deployments based on kub
## 5. Configuration
Several options are available on `cms/settings.py`, most of the things that are allowed or should be disallowed are described there.
It is advisable to override any of them by adding it to `local_settings.py`.
It is advisable to override any of them by adding it to `local_settings.py` .
**Single server installation:** edit `cms/local_settings.py`, make changes and restart MediaCMS:
In case of a the single server installation, add to `cms/local_settings.py` .
In case of a docker compose installation, add to `deploy/docker/local_settings.py` . This will automatically overwrite `cms/local_settings.py` .
Any change needs restart of MediaCMS in order to take effect.
Single server installation: edit `cms/local_settings.py`, make a change and restart MediaCMS
```bash
systemctl restart mediacms celery_beat celery_short celery_long
#systemctl restart mediacms
```
**Docker installation:** Configuration can be done in two ways:
1. **Environment variables** (recommended for simple changes):
Add to your docker-compose file:
```yaml
environment:
FRONTEND_HOST: 'https://mediacms.example.com'
PORTAL_NAME: 'My MediaCMS'
```
2. **Custom image with local_settings.py** (for complex changes):
- Create a custom Dockerfile:
```dockerfile
FROM mediacms/mediacms:7.3
COPY my_custom_settings.py /home/mediacms.io/mediacms/cms/local_settings.py
```
- Build and use your custom image
After changes, restart the affected containers:
Docker Compose installation: edit `deploy/docker/local_settings.py`, make a change and restart MediaCMS containers
```bash
docker compose restart web celery_short celery_long celery_beat
#docker compose restart web celery_worker celery_beat
```
### 5.1 Change portal logo

View File

@@ -46,7 +46,7 @@ Before beginning, ensure the following:
## Step 1: Configure MediaCMS for SAML
The first step in enabling SAML authentication is to modify the `local_settings.py` (for Docker: `./config/local_settings.py`) file of your MediaCMS deployment. Add the following configuration block to enable SAML support, role-based access control (RBAC), and enforce secure communication settings:
The first step in enabling SAML authentication is to modify the `local_settings.py` (for Docker: `./deploy/docker/local_settings.py`) file of your MediaCMS deployment. Add the following configuration block to enable SAML support, role-based access control (RBAC), and enforce secure communication settings:
```python
USE_RBAC = True
@@ -292,7 +292,7 @@ Another issue you might encounter is an **infinite redirect loop**. This can hap
https://<MyDomainName>/accounts/saml/mediacms_entraid/login/
```
* Add the following line to `./config/local_settings.py`:
* Add the following line to `./deploy/docker/local_settings.py`:
```python
LOGIN_URL = "/accounts/saml/mediacms_entraid/login/"

View File

@@ -329,10 +329,17 @@ class Media(models.Model):
if to_transcribe:
TranscriptionRequest.objects.create(media=self, translate_to_english=False)
tasks.whisper_transcribe.delay(self.friendly_token, translate_to_english=False)
tasks.whisper_transcribe.apply_async(
args=[self.friendly_token, False],
countdown=10,
)
if to_transcribe_and_translate:
TranscriptionRequest.objects.create(media=self, translate_to_english=True)
tasks.whisper_transcribe.delay(self.friendly_token, translate_to_english=True)
tasks.whisper_transcribe.apply_async(
args=[self.friendly_token, True],
countdown=10,
)
def update_search_vector(self):
"""

View File

@@ -20,7 +20,7 @@ const useVideoChapters = () => {
// Sort by start time to find chronological position
const sortedSegments = allSegments.sort((a, b) => a.startTime - b.startTime);
// Find the index of our new segment
const chapterIndex = sortedSegments.findIndex(seg => seg.startTime === newSegmentStartTime);
const chapterIndex = sortedSegments.findIndex((seg) => seg.startTime === newSegmentStartTime);
return `Chapter ${chapterIndex + 1}`;
};
@@ -28,12 +28,18 @@ const useVideoChapters = () => {
const renumberAllSegments = (segments: Segment[]): Segment[] => {
// Sort segments by start time
const sortedSegments = [...segments].sort((a, b) => a.startTime - b.startTime);
// Renumber each segment based on its chronological position
return sortedSegments.map((segment, index) => ({
...segment,
chapterTitle: `Chapter ${index + 1}`
}));
// Only update titles that follow the default "Chapter X" pattern to preserve custom titles
return sortedSegments.map((segment, index) => {
const currentTitle = segment.chapterTitle || '';
const isDefaultTitle = /^Chapter \d+$/.test(currentTitle);
return {
...segment,
chapterTitle: isDefaultTitle ? `Chapter ${index + 1}` : currentTitle,
};
});
};
// Helper function to parse time string (HH:MM:SS.mmm) to seconds
@@ -124,9 +130,7 @@ const useVideoChapters = () => {
let initialSegments: Segment[] = [];
// Check if we have existing chapters from the backend
const existingChapters =
(typeof window !== 'undefined' && (window as any).MEDIA_DATA?.chapters) ||
[];
const existingChapters = (typeof window !== 'undefined' && (window as any).MEDIA_DATA?.chapters) || [];
if (existingChapters.length > 0) {
// Create segments from existing chapters
@@ -225,7 +229,7 @@ const useVideoChapters = () => {
logger.debug('Adding Safari-specific event listeners for audio support');
video.addEventListener('canplay', handleCanPlay);
video.addEventListener('loadeddata', handleLoadedData);
// Additional timeout fallback for Safari audio files
const safariTimeout = setTimeout(() => {
if (video.duration && duration === 0) {
@@ -261,21 +265,21 @@ const useVideoChapters = () => {
useEffect(() => {
if (isSafari() && videoRef.current) {
const video = videoRef.current;
const initializeSafariOnInteraction = () => {
// Try to load video metadata by attempting to play and immediately pause
const attemptInitialization = async () => {
try {
logger.debug('Safari: Attempting auto-initialization on user interaction');
// Briefly play to trigger metadata loading, then pause
await video.play();
video.pause();
// Check if we now have duration and initialize if needed
if (video.duration > 0 && clipSegments.length === 0) {
logger.debug('Safari: Successfully initialized metadata, creating default segment');
const defaultSegment: Segment = {
id: 1,
chapterTitle: '',
@@ -286,14 +290,14 @@ const useVideoChapters = () => {
setDuration(video.duration);
setTrimEnd(video.duration);
setClipSegments([defaultSegment]);
const initialState: EditorState = {
trimStart: 0,
trimEnd: video.duration,
splitPoints: [],
clipSegments: [defaultSegment],
};
setHistory([initialState]);
setHistoryPosition(0);
}
@@ -315,7 +319,7 @@ const useVideoChapters = () => {
// Add listeners for various user interactions
document.addEventListener('click', handleUserInteraction);
document.addEventListener('keydown', handleUserInteraction);
return () => {
document.removeEventListener('click', handleUserInteraction);
document.removeEventListener('keydown', handleUserInteraction);
@@ -332,7 +336,7 @@ const useVideoChapters = () => {
// This play/pause will trigger metadata loading in Safari
await video.play();
video.pause();
// The metadata events should fire now and initialize segments
return true;
} catch (error) {
@@ -564,8 +568,11 @@ const useVideoChapters = () => {
`Updating segments with action: ${actionType}, recordHistory: ${isSignificantChange ? 'true' : 'false'}`
);
// Renumber all segments to ensure proper chronological naming
const renumberedSegments = renumberAllSegments(e.detail.segments);
// Update segment state immediately for UI feedback
setClipSegments(e.detail.segments);
setClipSegments(renumberedSegments);
// Always save state to history for non-intermediate actions
if (isSignificantChange) {
@@ -573,7 +580,7 @@ const useVideoChapters = () => {
// ensure we capture the state properly
setTimeout(() => {
// Deep clone to ensure state is captured correctly
const segmentsClone = JSON.parse(JSON.stringify(e.detail.segments));
const segmentsClone = JSON.parse(JSON.stringify(renumberedSegments));
// Create a complete state snapshot
const stateWithAction: EditorState = {
@@ -919,10 +926,10 @@ const useVideoChapters = () => {
const singleChapter = backendChapters[0];
const startSeconds = parseTimeToSeconds(singleChapter.startTime);
const endSeconds = parseTimeToSeconds(singleChapter.endTime);
// Check if this single chapter spans the entire video (within 0.1 second tolerance)
const isFullVideoChapter = startSeconds <= 0.1 && Math.abs(endSeconds - duration) <= 0.1;
if (isFullVideoChapter) {
logger.debug('Manual save: Single chapter spans full video - sending empty array');
backendChapters = [];

View File

@@ -1,51 +0,0 @@
#!/bin/bash
set -e
echo "========================================="
echo "MediaCMS Migrations Starting..."
echo "========================================="
# Wait for database to be ready
until python manage.py migrate --check 2>/dev/null; do
echo "Waiting for database to be ready..."
sleep 2
done
# Run migrations
echo "Running database migrations..."
python manage.py migrate
# Check if this is a new installation
EXISTING_INSTALLATION=$(echo "from users.models import User; print(User.objects.exists())" | python manage.py shell)
if [ "$EXISTING_INSTALLATION" = "True" ]; then
echo "Existing installation detected, skipping initial data load"
else
echo "New installation detected, loading initial data..."
# Load fixtures
python manage.py loaddata fixtures/encoding_profiles.json
python manage.py loaddata fixtures/categories.json
# Create admin user
RANDOM_ADMIN_PASS=$(python -c "import secrets;chars = 'abcdefghijklmnopqrstuvwxyz0123456789';print(''.join(secrets.choice(chars) for i in range(10)))")
ADMIN_PASSWORD=${ADMIN_PASSWORD:-$RANDOM_ADMIN_PASS}
DJANGO_SUPERUSER_PASSWORD=$ADMIN_PASSWORD python manage.py createsuperuser \
--no-input \
--username=${ADMIN_USER:-admin} \
--email=${ADMIN_EMAIL:-admin@localhost} \
--database=default || true
echo "========================================="
echo "Admin user created with password: $ADMIN_PASSWORD"
echo "========================================="
fi
# Collect static files
echo "Collecting static files..."
python manage.py collectstatic --noinput
echo "========================================="
echo "Migrations completed successfully!"
echo "========================================="

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long