A number of people have reported issues using the Docker version of CDVR-TVE with YTTV credentials. I've suspected this was related to the use of Chromium (open source), versus Chrome (based on Chromium, but proprietary).
After building a custom CDVR container today with Chrome, this does appear to be the case, as I had no issues with TVE authentication using YTTV (family account with no 2FA). The Linux version of Chrome is available for amd64 processors only, so this solution is limited to platforms using a compatible CPU.
Portainer makes it very simple to build an image for local use, so I'll detail what's required here:
First, on the system where you're running the Portainer WebUI, you'll need to create a file called run.sh containing this code (be sure to use an editor that supports Linux-standard linefeed only line endings):
#!/bin/sh
set -e
if [ ! -x /channels-dvr/latest/channels-dvr ]; then
echo Installing Channels DVR..
curl -f -s https://getchannels.com/dvr/setup.sh | DOWNLOAD_ONLY=1 sh
fi
ARGS="-dir /channels-dvr/data"
if [ -n "$CHANNELS_HOST" ]; then
ARGS="$ARGS -host $CHANNELS_HOST"
fi
if [ -n "$CHANNELS_PORT" ]; then
ARGS="$ARGS -port $CHANNELS_PORT"
fi
cd /channels-dvr/data
echo Running Channels DVR..
exec ../latest/channels-dvr $ARGS
Next, in Portainer, go to Images and select Build a new image. I'd suggest channels-dvr-local:tve for a name:
In the Web editor, paste in the following Dockerfile:
FROM debian:bookworm-slim
# Install dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
ca-certificates \
gnupg \
libglib2.0-0 \
libnss3 \
libx11-xcb1 \
libxcomposite1 \
libxdamage1 \
libxrandr2 \
libu2f-udev \
libvulkan1 \
libasound2 \
libatk-bridge2.0-0 \
libatk1.0-0 \
libcups2 \
libdbus-1-3 \
libdrm2 \
libgtk-3-0 \
libxshmfence1 \
fonts-liberation \
xdg-utils \
wget \
unzip \
&& rm -rf /var/lib/apt/lists/*
# Add Google Chrome's official GPG key and repo
RUN curl -fsSL https://dl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /etc/apt/trusted.gpg.d/google.gpg && \
echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list
# Install Google Chrome
RUN apt-get update && apt-get install -y --no-install-recommends \
google-chrome-stable \
&& rm -rf /var/lib/apt/lists/*
# Expose default web port and streaming port
EXPOSE 8089
ADD run.sh .
RUN chmod +x run.sh
# Run the DVR server
CMD ["/run.sh"]
And then, under Upload, select the run.sh file you created on your local system:
Finally, click Build the image under Actions.
If everything goes according to plan, you should see output like this:
Step 1/8 : FROM debian:bookworm-slim
---> 6ba4ddadd113
Step 2/8 : RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates gnupg libglib2.0-0 libnss3 libx11-xcb1 libxcomposite1 libxdamage1 libxrandr2 libu2f-udev libvulkan1 libasound2 libatk-bridge2.0-0 libatk1.0-0 libcups2 libdbus-1-3 libdrm2 libgtk-3-0 libxshmfence1 fonts-liberation xdg-utils wget unzip && rm -rf /var/lib/apt/lists/*
---> Using cache
---> 53b3b4daa5b8
Step 3/8 : RUN curl -fsSL https://dl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /etc/apt/trusted.gpg.d/google.gpg && echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list
---> Using cache
---> 003a26b56132
Step 4/8 : RUN apt-get update && apt-get install -y --no-install-recommends google-chrome-stable && rm -rf /var/lib/apt/lists/*
---> Using cache
---> fba0b47ca738
Step 5/8 : EXPOSE 8089
---> Using cache
---> 2bd3d1a53aa1
Step 6/8 : ADD run.sh .
---> Using cache
---> 58e790b406ca
Step 7/8 : RUN chmod +x run.sh
---> Using cache
---> feae921f466e
Step 8/8 : CMD ["run.sh"]
---> Running in 39aa11b3f439
---> Removed intermediate container 39aa11b3f439
---> 0a7299aad012
Successfully built 0a7299aad012
Successfully tagged channels-dvr-local:tve
Look good? If so, you now have a custom built local CDVR image containing Chrome.
Next, you need spin-up this container in Portainer-Stacks. The Docker compose should look similar to this:
version: '3.9'
services:
# 2025.06.20
# Docker Hub home for this project: https://hub.docker.com/r/fancybits/channels-dvr
channels-dvr:
image: channels-dvr-local:${TAG}
container_name: channels-dvr-local
devices:
- /dev/dri:/dev/dri
ports:
- ${HOST_PORT}:${CHANNELS_PORT}
environment:
- CHANNELS_PORT=${CHANNELS_PORT}
- TZ=${TZ}
volumes:
- ${HOST_DIR}/channels-dvr-local:/channels-dvr
- ${DVR_SHARE}:${DVR_CONTAINER_DIR}
network_mode: host
restart: unless-stopped
#volumes: # use this section if you've setup a docker volume named channels-dvr, with CIFS or NFS, to bind to /channels-dvr inside the container
#channels-dvr:
#external: ${VOL_EXTERNAL}
#name: ${VOL_NAME}
And here are the env vars I used, but yours will vary based on where you want your channels-dvr and channels-data directories to reside:
TAG=tve
HOST_PORT=8089
CHANNELS_PORT=8089
TZ=US/Mountain
HOST_DIR=/data
DVR_SHARE=/data/channels-data-local
DVR_CONTAINER_DIR=/data/channels-data-local
This should function very much like the standard channels-dvr:tve container. Channels will update itself as per usual, however if you wish to update Chrome (in a manner that will survive reboots), you'll need to rebuild the container.
Hopefully someone will be willing to confirm this solution, and once that's done, I'll likely make this process Project One-Click compatible -- both for building a new image, and deploying it.


-- so I was running the previous build.