From 1d043702ef136e8f2e516c5304a57be5718f1f6f Mon Sep 17 00:00:00 2001 From: Tyler Hoang Date: Thu, 9 Jul 2026 21:49:56 -0700 Subject: Initial commit: docker compose files for all self-hosted services 12 services: adguard, ampache, azuracast, flood, glance, glances, grafana, immich, invidious, jellyfin, openwebui, watchtower Each service includes a docker-compose.yml, README.md with setup instructions, and .env.example where applicable. --- invidious/README.md | 28 +++++++++++ invidious/config/sql/annotations.sql | 12 +++++ invidious/config/sql/channel_videos.sql | 30 ++++++++++++ invidious/config/sql/channels.sql | 25 ++++++++++ invidious/config/sql/nonces.sql | 22 +++++++++ invidious/config/sql/playlist_videos.sql | 19 ++++++++ invidious/config/sql/playlists.sql | 29 ++++++++++++ invidious/config/sql/session_ids.sql | 23 +++++++++ invidious/config/sql/users.sql | 29 ++++++++++++ invidious/config/sql/videos.sql | 23 +++++++++ invidious/docker-compose.yml | 81 ++++++++++++++++++++++++++++++++ invidious/docker/Dockerfile | 81 ++++++++++++++++++++++++++++++++ invidious/docker/init-invidious-db.sh | 12 +++++ 13 files changed, 414 insertions(+) create mode 100644 invidious/README.md create mode 100644 invidious/config/sql/annotations.sql create mode 100644 invidious/config/sql/channel_videos.sql create mode 100644 invidious/config/sql/channels.sql create mode 100644 invidious/config/sql/nonces.sql create mode 100644 invidious/config/sql/playlist_videos.sql create mode 100644 invidious/config/sql/playlists.sql create mode 100644 invidious/config/sql/session_ids.sql create mode 100644 invidious/config/sql/users.sql create mode 100644 invidious/config/sql/videos.sql create mode 100644 invidious/docker-compose.yml create mode 100644 invidious/docker/Dockerfile create mode 100755 invidious/docker/init-invidious-db.sh (limited to 'invidious') diff --git a/invidious/README.md b/invidious/README.md new file mode 100644 index 0000000..fbfa29f --- /dev/null +++ b/invidious/README.md @@ -0,0 +1,28 @@ +# Invidious + +Self-hosted YouTube frontend. No ads, no tracking. + +## Setup + +1. Generate required keys: + ``` + docker compose run --rm invidious --generate-hmac-key + docker compose run --rm invidious --generate-api-key + ``` + +2. Set the generated keys as environment variables and start: + ``` + INVIDIOUS_HMAC_KEY= INVIDIOUS_COMPANION_KEY= docker compose up -d + ``` + + Or add them to an `.env` file (create one in this directory). + +## Access + +- Web UI: `http://:3000` + +## What to Configure + +- **Keys** — `INVIDIOUS_HMAC_KEY` and `INVIDIOUS_COMPANION_KEY` must be set. Without them, Invidious will not work. +- **Database password** — Defaults to `kemal`. Change via `POSTGRES_PASSWORD` env var for production. +- **Database init** — `docker/init-invidious-db.sh` and `config/sql/` are used for first-time database setup. No changes needed unless you customize the schema. diff --git a/invidious/config/sql/annotations.sql b/invidious/config/sql/annotations.sql new file mode 100644 index 0000000..3705829 --- /dev/null +++ b/invidious/config/sql/annotations.sql @@ -0,0 +1,12 @@ +-- Table: public.annotations + +-- DROP TABLE public.annotations; + +CREATE TABLE IF NOT EXISTS public.annotations +( + id text NOT NULL, + annotations xml, + CONSTRAINT annotations_id_key UNIQUE (id) +); + +GRANT ALL ON TABLE public.annotations TO current_user; diff --git a/invidious/config/sql/channel_videos.sql b/invidious/config/sql/channel_videos.sql new file mode 100644 index 0000000..cd4e0ff --- /dev/null +++ b/invidious/config/sql/channel_videos.sql @@ -0,0 +1,30 @@ +-- Table: public.channel_videos + +-- DROP TABLE public.channel_videos; + +CREATE TABLE IF NOT EXISTS public.channel_videos +( + id text NOT NULL, + title text, + published timestamp with time zone, + updated timestamp with time zone, + ucid text, + author text, + length_seconds integer, + live_now boolean, + premiere_timestamp timestamp with time zone, + views bigint, + CONSTRAINT channel_videos_id_key UNIQUE (id) +); + +GRANT ALL ON TABLE public.channel_videos TO current_user; + +-- Index: public.channel_videos_ucid_idx + +-- DROP INDEX public.channel_videos_ucid_idx; + +CREATE INDEX IF NOT EXISTS channel_videos_ucid_idx + ON public.channel_videos + USING btree + (ucid COLLATE pg_catalog."default"); + diff --git a/invidious/config/sql/channels.sql b/invidious/config/sql/channels.sql new file mode 100644 index 0000000..55772da --- /dev/null +++ b/invidious/config/sql/channels.sql @@ -0,0 +1,25 @@ +-- Table: public.channels + +-- DROP TABLE public.channels; + +CREATE TABLE IF NOT EXISTS public.channels +( + id text NOT NULL, + author text, + updated timestamp with time zone, + deleted boolean, + subscribed timestamp with time zone, + CONSTRAINT channels_id_key UNIQUE (id) +); + +GRANT ALL ON TABLE public.channels TO current_user; + +-- Index: public.channels_id_idx + +-- DROP INDEX public.channels_id_idx; + +CREATE INDEX IF NOT EXISTS channels_id_idx + ON public.channels + USING btree + (id COLLATE pg_catalog."default"); + diff --git a/invidious/config/sql/nonces.sql b/invidious/config/sql/nonces.sql new file mode 100644 index 0000000..644ac32 --- /dev/null +++ b/invidious/config/sql/nonces.sql @@ -0,0 +1,22 @@ +-- Table: public.nonces + +-- DROP TABLE public.nonces; + +CREATE TABLE IF NOT EXISTS public.nonces +( + nonce text, + expire timestamp with time zone, + CONSTRAINT nonces_id_key UNIQUE (nonce) +); + +GRANT ALL ON TABLE public.nonces TO current_user; + +-- Index: public.nonces_nonce_idx + +-- DROP INDEX public.nonces_nonce_idx; + +CREATE INDEX IF NOT EXISTS nonces_nonce_idx + ON public.nonces + USING btree + (nonce COLLATE pg_catalog."default"); + diff --git a/invidious/config/sql/playlist_videos.sql b/invidious/config/sql/playlist_videos.sql new file mode 100644 index 0000000..4b48b46 --- /dev/null +++ b/invidious/config/sql/playlist_videos.sql @@ -0,0 +1,19 @@ +-- Table: public.playlist_videos + +-- DROP TABLE public.playlist_videos; + +CREATE TABLE IF NOT EXISTS public.playlist_videos +( + title text, + id text, + author text, + ucid text, + length_seconds integer, + published timestamptz, + plid text references playlists(id), + index int8, + live_now boolean, + PRIMARY KEY (index,plid) +); + +GRANT ALL ON TABLE public.playlist_videos TO current_user; diff --git a/invidious/config/sql/playlists.sql b/invidious/config/sql/playlists.sql new file mode 100644 index 0000000..83efce4 --- /dev/null +++ b/invidious/config/sql/playlists.sql @@ -0,0 +1,29 @@ +-- Type: public.privacy + +-- DROP TYPE public.privacy; + +CREATE TYPE public.privacy AS ENUM +( + 'Public', + 'Unlisted', + 'Private' +); + +-- Table: public.playlists + +-- DROP TABLE public.playlists; + +CREATE TABLE IF NOT EXISTS public.playlists +( + title text, + id text primary key, + author text, + description text, + video_count integer, + created timestamptz, + updated timestamptz, + privacy privacy, + index int8[] +); + +GRANT ALL ON public.playlists TO current_user; diff --git a/invidious/config/sql/session_ids.sql b/invidious/config/sql/session_ids.sql new file mode 100644 index 0000000..c493769 --- /dev/null +++ b/invidious/config/sql/session_ids.sql @@ -0,0 +1,23 @@ +-- Table: public.session_ids + +-- DROP TABLE public.session_ids; + +CREATE TABLE IF NOT EXISTS public.session_ids +( + id text NOT NULL, + email text, + issued timestamp with time zone, + CONSTRAINT session_ids_pkey PRIMARY KEY (id) +); + +GRANT ALL ON TABLE public.session_ids TO current_user; + +-- Index: public.session_ids_id_idx + +-- DROP INDEX public.session_ids_id_idx; + +CREATE INDEX IF NOT EXISTS session_ids_id_idx + ON public.session_ids + USING btree + (id COLLATE pg_catalog."default"); + diff --git a/invidious/config/sql/users.sql b/invidious/config/sql/users.sql new file mode 100644 index 0000000..ad002ec --- /dev/null +++ b/invidious/config/sql/users.sql @@ -0,0 +1,29 @@ +-- Table: public.users + +-- DROP TABLE public.users; + +CREATE TABLE IF NOT EXISTS public.users +( + updated timestamp with time zone, + notifications text[], + subscriptions text[], + email text NOT NULL, + preferences text, + password text, + token text, + watched text[], + feed_needs_update boolean, + CONSTRAINT users_email_key UNIQUE (email) +); + +GRANT ALL ON TABLE public.users TO current_user; + +-- Index: public.email_unique_idx + +-- DROP INDEX public.email_unique_idx; + +CREATE UNIQUE INDEX IF NOT EXISTS email_unique_idx + ON public.users + USING btree + (lower(email) COLLATE pg_catalog."default"); + diff --git a/invidious/config/sql/videos.sql b/invidious/config/sql/videos.sql new file mode 100644 index 0000000..55da396 --- /dev/null +++ b/invidious/config/sql/videos.sql @@ -0,0 +1,23 @@ +-- Table: public.videos + +-- DROP TABLE public.videos; + +CREATE UNLOGGED TABLE IF NOT EXISTS public.videos +( + id text NOT NULL, + info text, + updated timestamp with time zone, + CONSTRAINT videos_pkey PRIMARY KEY (id) +); + +GRANT ALL ON TABLE public.videos TO current_user; + +-- Index: public.id_idx + +-- DROP INDEX public.id_idx; + +CREATE UNIQUE INDEX IF NOT EXISTS id_idx + ON public.videos + USING btree + (id COLLATE pg_catalog."default"); + diff --git a/invidious/docker-compose.yml b/invidious/docker-compose.yml new file mode 100644 index 0000000..c261a38 --- /dev/null +++ b/invidious/docker-compose.yml @@ -0,0 +1,81 @@ +services: + invidious: + image: quay.io/invidious/invidious:latest + restart: unless-stopped + # Remove "127.0.0.1:" if used from an external IP + ports: + - "3000:3000" + environment: + # Please read the following file for a comprehensive list of all available + # configuration options and their associated syntax: + # https://github.com/iv-org/invidious/blob/master/config/config.example.yml + INVIDIOUS_CONFIG: | + db: + dbname: invidious + user: kemal + password: ${POSTGRES_PASSWORD:-kemal} + host: invidious-db + port: 5432 + check_tables: true + invidious_companion: + - private_url: "http://companion:8282/companion" + # Use the key generated in the 2nd step + invidious_companion_key: "${INVIDIOUS_COMPANION_KEY}" + # Use the key generated in the 1st step + hmac_key: "${INVIDIOUS_HMAC_KEY}" + healthcheck: + test: wget -nv --tries=1 --spider http://127.0.0.1:3000/api/v1/stats || exit 1 + interval: 30s + timeout: 5s + retries: 2 + logging: + options: + max-size: "1G" + max-file: "4" + depends_on: + - invidious-db + + companion: + image: quay.io/invidious/invidious-companion:latest + # Please read the following file for a comprehensive list of all available + # environment variables and their associated syntax: + # https://github.com/iv-org/invidious/blob/master/config/config.example.yml + environment: + # Use the key generated in the 2nd step + - SERVER_SECRET_KEY=${INVIDIOUS_COMPANION_KEY} + restart: unless-stopped + # Uncomment only if you have configured "public_url" for Invidious companion + # Or if you want to use Invidious companion as an API in your program. + # Remove "127.0.0.1:" if used from an external IP + #ports: + # - "127.0.0.1:8282:8282" + logging: + options: + max-size: "1G" + max-file: "4" + cap_drop: + - ALL + read_only: true + # cache for youtube library + volumes: + - companioncache:/var/tmp/youtubei.js:rw + security_opt: + - no-new-privileges:true + + invidious-db: + image: docker.io/library/postgres:14 + restart: unless-stopped + volumes: + - postgresdata:/var/lib/postgresql/data + - ./config/sql:/config/sql + - ./docker/init-invidious-db.sh:/docker-entrypoint-initdb.d/init-invidious-db.sh + environment: + POSTGRES_DB: invidious + POSTGRES_USER: kemal + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-kemal} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] + +volumes: + postgresdata: + companioncache: diff --git a/invidious/docker/Dockerfile b/invidious/docker/Dockerfile new file mode 100644 index 0000000..f837dae --- /dev/null +++ b/invidious/docker/Dockerfile @@ -0,0 +1,81 @@ +# https://github.com/openssl/openssl/releases/tag/openssl-3.6.2 +ARG OPENSSL_VERSION='3.6.2' +ARG OPENSSL_SHA256='aaf51a1fe064384f811daeaeb4ec4dce7340ec8bd893027eee676af31e83a04f' + +FROM 84codes/crystal:1.20.2-alpine AS dependabot-crystal + +# We compile openssl ourselves due to a memory leak in how crystal interacts +# with openssl +# Reference: https://github.com/iv-org/invidious/issues/1438#issuecomment-3087636228 +FROM dependabot-crystal AS openssl-builder +RUN apk add --no-cache curl perl linux-headers + +WORKDIR / + +ARG OPENSSL_VERSION +ARG OPENSSL_SHA256 +RUN curl -Ls "https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_VERSION}/openssl-${OPENSSL_VERSION}.tar.gz" --output openssl-${OPENSSL_VERSION}.tar.gz +RUN echo "${OPENSSL_SHA256} openssl-${OPENSSL_VERSION}.tar.gz" | sha256sum -c +RUN tar -xzvf openssl-${OPENSSL_VERSION}.tar.gz + +RUN cd openssl-${OPENSSL_VERSION} && ./Configure --openssldir=/etc/ssl && make -j$(nproc) + +FROM dependabot-crystal AS builder + +RUN apk add --no-cache sqlite-static yaml-static +RUN apk del openssl-dev openssl-libs-static + +ARG release + +WORKDIR /invidious +COPY ./shard.yml ./shard.yml +COPY ./shard.lock ./shard.lock +RUN shards install --production + +COPY ./src/ ./src/ +# TODO: .git folder is required for building – this is destructive. +# See definition of CURRENT_BRANCH, CURRENT_COMMIT and CURRENT_VERSION. +COPY ./.git/ ./.git/ + +# Required for fetching player dependencies +COPY ./scripts/ ./scripts/ +COPY ./assets/ ./assets/ +COPY ./videojs-dependencies.yml ./videojs-dependencies.yml + +RUN crystal spec --warnings all \ + --link-flags "-lxml2 -llzma" + +ARG OPENSSL_VERSION +COPY --from=openssl-builder /openssl-${OPENSSL_VERSION} /openssl-${OPENSSL_VERSION} + +RUN --mount=type=cache,target=/root/.cache/crystal if [[ "${release}" == 1 ]] ; then \ + PKG_CONFIG_PATH=/openssl-${OPENSSL_VERSION} \ + crystal build ./src/invidious.cr \ + --release \ + --static --warnings all \ + --link-flags "-lxml2 -llzma"; \ + else \ + PKG_CONFIG_PATH=/openssl-${OPENSSL_VERSION} \ + crystal build ./src/invidious.cr \ + --static --warnings all \ + --link-flags "-lxml2 -llzma"; \ + fi + +FROM alpine:3.23 +RUN apk add --no-cache rsvg-convert ttf-opensans tini tzdata +WORKDIR /invidious +RUN addgroup -g 1000 -S invidious && \ + adduser -u 1000 -S invidious -G invidious +COPY --chown=invidious ./config/config.* ./config/ +RUN mv -n config/config.example.yml config/config.yml +RUN sed -i 's/host: \(127.0.0.1\|localhost\)/host: invidious-db/' config/config.yml +COPY ./config/sql/ ./config/sql/ +COPY ./locales/ ./locales/ +COPY --from=builder /invidious/assets ./assets/ +COPY --from=builder /invidious/invidious . +RUN chmod o+rX -R ./assets ./config ./locales + +EXPOSE 3000 +USER invidious +ENTRYPOINT ["/sbin/tini", "--"] +CMD [ "/invidious/invidious" ] diff --git a/invidious/docker/init-invidious-db.sh b/invidious/docker/init-invidious-db.sh new file mode 100755 index 0000000..22b4cc5 --- /dev/null +++ b/invidious/docker/init-invidious-db.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -eou pipefail + +psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/channels.sql +psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/videos.sql +psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/channel_videos.sql +psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/users.sql +psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/session_ids.sql +psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/nonces.sql +psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/annotations.sql +psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/playlists.sql +psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/playlist_videos.sql -- cgit v1.3-2-g0d8e