Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.git
.github
.gitignore
docs
examples
CONTRIBUTING.md
CLAUDE.md
build
node_modules
scripts/node_modules
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,8 @@ The build script:
5. Generates clean HTML entry points that load only the two bundles
6. Copies `_server/`, `.htaccess`, `favico.png`, `LICENSE.md`

**Docker:** `Dockerfile` + `compose.yaml` in the repo root run this same build in a throwaway Node stage and serve the output via PHP + Apache (`docker compose up -d`). See [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md#option-1-server-deployment-php) for build args (e.g. `WITH_FFMPEG`) and volume details.

### CI/CD

- **Build verification** (`.github/workflows/build.yml`): Runs on every push to `main`/`develop` and every PR. Builds and verifies output.
Expand Down
76 changes: 76 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# syntax=docker/dockerfile:1
#
# Multi-stage build: compiles the minified JS/CSS bundle from source in a
# throwaway Node stage (mirrors scripts/build.sh / .github/workflows/build.yml),
# then serves it from a plain PHP + Apache runtime with no build tooling included.
#
# Build: docker build -t frametrail .
# Run: docker run -p 8080:80 -v frametrail_data:/var/www/html/_data frametrail

########################################
# Stage 1: build minified JS/CSS bundle
########################################
FROM node:20-alpine AS build

RUN apk add --no-cache bash

WORKDIR /src
COPY . .

RUN npm install -g terser csso-cli \
&& bash scripts/build.sh

########################################
# Stage 2: runtime (PHP + Apache)
########################################
FROM php:8.3-apache

# ffmpeg adds ~500MB (codecs/libs) for a feature (server-side video transcoding +
# thumbnail/scrub-sprite generation) the app detects at runtime and silently skips
# if absent. Off by default for a lean image; opt in with --build-arg WITH_FFMPEG=true.
ARG WITH_FFMPEG=false

# gd -> image thumbnails (src/_server/files.php)
# curl -> oEmbed / OpenGraph resource previews
RUN apt-get update && apt-get install -y --no-install-recommends \
libfreetype6-dev \
libjpeg62-turbo-dev \
libpng-dev \
libcurl4-openssl-dev \
$( [ "$WITH_FFMPEG" = "true" ] && echo ffmpeg ) \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j"$(nproc)" gd curl \
&& a2enmod rewrite \
&& printf '<Directory /var/www/html>\n AllowOverride All\n</Directory>\n' \
>> /etc/apache2/apache2.conf \
# gd.so / curl.so link against these at runtime. Re-installing them by name
# marks them "manually installed" so the --auto-remove purge below doesn't
# treat them as orphaned deps of the -dev packages and remove them too.
&& apt-get install -y --no-install-recommends \
libfreetype6 libjpeg62-turbo libpng16-16t64 libcurl4t64 \
&& apt-get purge -y --auto-remove \
libfreetype6-dev libjpeg62-turbo-dev libpng-dev libcurl4-openssl-dev \
&& rm -rf /var/lib/apt/lists/* \
# Fail the build loudly (rather than shipping a broken image) if a future base
# image bump renames the runtime lib packages above and breaks extension loading.
&& php -m | grep -qx gd && php -m | grep -qx curl

WORKDIR /var/www/html
COPY --from=build /src/build/ ./

# Install dir + _data/ must be writable by the web server: FrameTrail's setup
# wizard creates _data/ on first run and writes into it on every save/upload.
RUN chown -R www-data:www-data /var/www/html

# _data/ doesn't exist yet at this point (it's created by the setup wizard), so
# this chown never reaches it once a volume/bind-mount is placed there at
# `docker run`/`compose up` time — mounting always overrides build-time state
# with a fresh, root-owned directory. docker-entrypoint.sh fixes ownership at
# container start instead, where the mounted path actually exists.
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh

EXPOSE 80

ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["apache2-foreground"]
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ FrameTrail works in three modes with different capabilities.

For public deployments, use Apache (`.htaccess` included) or nginx with PHP-FPM. No PHP installed? Use [XAMPP](https://www.apachefriends.org/) (Windows) or [MAMP](https://www.mamp.info/) (Mac/Windows).

**Or use Docker** — no PHP install needed:

```bash
docker compose up -d
```

Open `http://localhost:8080` and follow the setup wizard. The image builds the minified bundle from source in a throwaway Node stage and serves it via PHP + Apache, so no build tooling ends up in the final image; `_data/` persists in a named volume across restarts. Pass `--build-arg WITH_FFMPEG=true` (or uncomment the `args:` in `compose.yaml`) to include FFmpeg for server-side video transcoding and thumbnail generation — it's off by default to keep the image lean (~500MB smaller).

### Option 2: Local Folder Mode

1. Download and extract FrameTrail
Expand Down
16 changes: 16 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
services:
frametrail:
build:
context: .
# args:
# WITH_FFMPEG: "true" # adds ffmpeg for server-side transcoding (~500MB larger)
ports:
- "8080:80"
volumes:
# All app state (accounts, hypervideos, annotations, uploads) lives here —
# no database. Back this up / migrate between installs by copying it.
- frametrail_data:/var/www/html/_data
restart: unless-stopped

volumes:
frametrail_data:
15 changes: 15 additions & 0 deletions docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/sh
set -e

# _data/ doesn't exist in the image (FrameTrail's setup wizard creates it on
# first run), so a fresh volume/bind-mount is initialized root-owned by Docker.
# The chown baked into the image at build time never touches it, since it's
# applied before any volume is ever mounted over the path. Fix ownership here,
# at container start, once — skip it once it's already correct so a large
# _data/ doesn't pay a recursive chown on every restart.
mkdir -p /var/www/html/_data
if [ "$(stat -c '%U' /var/www/html/_data)" != "www-data" ]; then
chown -R www-data:www-data /var/www/html/_data
fi

exec docker-php-entrypoint "$@"
18 changes: 18 additions & 0 deletions docs/DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,24 @@ Then open `http://localhost:8080`. No Apache, no XAMPP needed if PHP is installe
- `.htaccess` — Apache rewrite rules
- `favico.png`, `README.md`, `LICENSE.md`

**Or use Docker:**

```bash
git clone https://github.com/OpenHypervideo/FrameTrail.git
cd FrameTrail
docker compose up -d
```

Open `http://localhost:8080` and follow the setup wizard. The `Dockerfile` builds the minified bundle from source in a throwaway Node stage (mirrors `scripts/build.sh`) and serves it via PHP + Apache — no Node or build tooling ends up in the final image. `_data/` persists in the named `frametrail_data` volume across container restarts/upgrades.

FFmpeg (server-side video transcoding + thumbnail/scrub-sprite generation) is left out by default to keep the image lean (~500MB smaller); the app detects its absence at runtime and skips those features gracefully. To include it:

```bash
docker compose build --build-arg WITH_FFMPEG=true
```

or uncomment the `args:` block in `compose.yaml`.

### Option 2: Local Folder Mode (No Server)

Single-user editing without a server. Uses the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API) to read and write files directly on disk. File uploads (images, video, audio) are supported, but server-side capabilities like media transcoding and user management are not available.
Expand Down