GRVYDEV / Project-Lightspeed

A self contained OBS -> FTL -> WebRTC live streaming server. Comprised of 3 parts once configured anyone can achieve sub-second OBS to the browser livestreaming
MIT License
3.64k stars 138 forks source link

Docker + Docker Compose #2

Closed brettwilcox closed 3 years ago

brettwilcox commented 3 years ago

Docker + Docker Compose

The project deployment needs to be simplified with a docker and docker-compose structure. We need a pull request to address adding this as a first class support model.

Samuel-Martineau commented 3 years ago

Couldn’t you use flairs/tags?

brettwilcox commented 3 years ago

Another note, the reason I do not want a monorepo is that I would like to keep issue tracking separate since it makes it easier to manage

100% okay with this. I would still not use submodules and just use this as the build repository for docker and documentation. The docker-compose.yml will pull the images from docker hub and those images are triggered from each repository using GitHub Actions.

I'm just trying to convey that you will 100% have issues with submodules no matter how perfectly maintained they are. It's just near impossible since users are not very familiar with them as they are not used very often.

GRVYDEV commented 3 years ago

Another note, the reason I do not want a monorepo is that I would like to keep issue tracking separate since it makes it easier to manage

100% okay with this. I would still not use submodules and just use this as the build repository for docker and documentation. The docker-compose.yml will pull the images from docker hub and those images are triggered from each repository using GitHub Actions.

I'm just trying to convey that you will 100% have issues with submodules no matter how perfectly maintained they are. It's just near impossible since users are not very familiar with them as they are not used very often.

Understood and will stay away from them!

GRVYDEV commented 3 years ago

Couldn’t you use flairs/tags?

Absolutely but I dont think this is what I want for this project

GRVYDEV commented 3 years ago

@brettwilcox Okay so here is an issue. Right now you need to edit a dockerfile for the react website in order to make sure your websocket url is correct. How would I remedy this with a premade image? Is there a way I can use a flag or env var?

brettwilcox commented 3 years ago

Below is an example pattern I use.

.env

IMAGE=myimage
TAG=1.2.3

docker-compose.yml

services:
  my-app:
    env_file:
      - ".env"
    labels:
      - "IMAGE=${IMAGE}"
      - "TAG=${TAG}"
    build:
      context: ./docker/my-app
      args:
        - "IMAGE=${IMAGE}"
        - "TAG=${TAG}"
    restart: always

./docker/my-app/Dockerfile

ARG IMAGE=${IMAGE}
ARG TAG=${TAG}

# Image and version to pull
FROM ${IMAGE}:${TAG}
brettwilcox commented 3 years ago

I've slept since I did this...

brettwilcox commented 3 years ago

You would have a .env.example file in the root with the documented environment variables.

shish commented 3 years ago

How would I remedy this with a premade image? Is there a way I can use a flag or env var?

IIRC ARG in dockerfiles is used for build-time variables - if we want to pre-build a docker image, and have websocket URL as a run-time variable, we'd want ENV, so the Dockerfile would include stuff like:

ENV WS_URL=localhost
CMD sed -i "s|stream.gud.software|$WS_URL|g" public/config.json && serve -s build -l 80

(Then you can override WS_URL using whatever mechanism (docker-compose.yml, .env, docker -e, ...) and it'll be inserted into the config file at runtime)

k3d3 commented 3 years ago

I've made a few PRs to each of the repos. Right now, in hopes that this is useful information, I have everything running from these commands and this docker-compose.yml file:

(to build the docker images locally)

mkdir lightspeed
cd lightspeed
git clone https://github.com/GRVYDEV/Lightspeed-ingest/
cd Lightspeed-ingest
docker build . -t lightspeed-ingest-test
cd ..
git clone https://github.com/GRVYDEV/Lightspeed-webrtc/
cd Lightspeed-webrtc
docker build . -t lightspeed-webrtc-test
cd ..
git clone https://github.com/GRVYDEV/Lightspeed-react/
cd Lightspeed-react
sed -i "s|stream.gud.software:8080|<my public domain>:8085|g" src/wsUrl.js
docker build . -t lightspeed-react-test
cd ..

(docker-compose.yml)

version: '3.7'
services:
    lightspeed_ingest:
        image: lightspeed-ingest-test
        container_name: lightspeed_ingest
        restart: unless-stopped
        ports:
            - 8084:8084

    lightspeed_webrtc:
        image: lightspeed-webrtc-test
        container_name: lightspeed_webrtc
        restart: unless-stopped
        entrypoint: lightspeed-webrtc
        command: --addr=0.0.0.0 --ip=<my public IP> --ports=19050-19100
        ports:
            - 8085:8080 # WebRTC
            - 65535:65535/udp # RTP
            - 19050-19100:19050-19100 # WebRTC PeerConnection
            - 19050-19100:19050-19100/udp # WebRTC PeerConnection UDP

    lightspeed_react:
        image: lightspeed-react-test
        container_name: lightspeed_react
        restart: unless-stopped
        ports:
            - 8086:80

Now from this setup, I have the react web server running on port 8086, the websocket server on port 8085, and the FTL port on 8084. I also have port 65535 forwarded for RTP and ports 19050-19100 forwarded for WebRTC ICE connections.

Once these images exist on docker hub, I'm sure this setup will become much simpler.

Hope that's helpful!

GRVYDEV commented 3 years ago

I've made a few PRs to each of the repos. Right now, in hopes that this is useful information, I have everything running from these commands and this docker-compose.yml file:

(to build the docker images locally)

mkdir lightspeed
cd lightspeed
git clone https://github.com/GRVYDEV/Lightspeed-ingest/
cd Lightspeed-ingest
docker build . -t lightspeed-ingest-test
cd ..
git clone https://github.com/GRVYDEV/Lightspeed-webrtc/
cd Lightspeed-webrtc
docker build . -t lightspeed-webrtc-test
cd ..
git clone https://github.com/GRVYDEV/Lightspeed-react/
cd Lightspeed-react
sed -i "s|stream.gud.software:8080|<my public domain>:8085|g" src/wsUrl.js
docker build . -t lightspeed-react-test
cd ..

(docker-compose.yml)

version: '3.7'
services:
    lightspeed_ingest:
        image: lightspeed-ingest-test
        container_name: lightspeed_ingest
        restart: unless-stopped
        ports:
            - 8084:8084

    lightspeed_webrtc:
        image: lightspeed-webrtc-test
        container_name: lightspeed_webrtc
        restart: unless-stopped
        entrypoint: lightspeed-webrtc
        command: --addr=0.0.0.0 --ip=<my public IP> --ports=19050-19100
        ports:
            - 8085:8080 # WebRTC
            - 65535:65535/udp # RTP
            - 19050-19100:19050-19100 # WebRTC PeerConnection
            - 19050-19100:19050-19100/udp # WebRTC PeerConnection UDP

    lightspeed_react:
        image: lightspeed-react-test
        container_name: lightspeed_react
        restart: unless-stopped
        ports:
            - 8086:80

Now from this setup, I have the react web server running on port 8086, the websocket server on port 8085, and the FTL port on 8084. I also have port 65535 forwarded for RTP and ports 19050-19100 forwarded for WebRTC ICE connections.

Once these images exist on docker hub, I'm sure this setup will become much simpler.

Hope that's helpful!

Awesome work! Thank you so much! I am going to work on getting the images on docker hub ASAP along with github actions for building them

GRVYDEV commented 3 years ago

@k3d3 @brettwilcox Is there a way I could setup a stage in docker-compose file that would tell the lightspeed-react image to run the sed -i command with a provided value? What I mean is if the main repo contained a docker compose and a .env file is there a way someone could edit the .env file and then the compose file would edit the proper values in each image? Im trying to figure out a solution that is basically clone the main repo, change a couple lines in a .env file and boom itll work

qdm12 commented 3 years ago

You could technically put a variable in an entrypoint: sed ${VARIABLE} && /entrypoint or command: block, which could be read from a .env file.

As a side note, I wrote reactserv which reads static react files from disk, and serves them from memory as well as modify them to set the root url in the react code. You could use a similar approach to serve your react code as well as replace values at start through an env variable. That way you can even have the final image based on scratch (no OS) as long as you use go/rust and compile statically.

GRVYDEV commented 3 years ago

You could technically put a variable in an entrypoint: sed ${VARIABLE} && /entrypoint or command: block, which could be read from a .env file.

As a side note, I wrote reactserv which reads static react files from disk, and serves them from memory as well as modify them to set the root url in the react code. You could use a similar approach to serve your react code as well as replace values at start through an env variable. That way you can even have the final image based on scratch (no OS) as long as you use go/rust and compile statically.

So theoretically in the docker compose file I could do command: sed -i "s|stream.gud.software|ENV_VAR|g" build/config.json && serve -s build -l 80 which would replace this command

Woodham commented 3 years ago

A simple solution to overriding the websocket url in a running container is to just document how to provide your own config.json file using docker mounts.

k3d3 commented 3 years ago

While I'm not sure of the best solution, I would also like some way to change the port number as well, since I'm using port 8085 in my setup.

k3d3 commented 3 years ago

I do like that idea too - providing a config.json via docker volume - but that also seems pretty heavyweight for just a url change.

GRVYDEV commented 3 years ago

While I'm not sure of the best solution, I would also like some way to change the port number as well, since I'm using port 8085 in my setup.

Yup the plan is to have the config change the entire url including port. It sounds like we may mount a custom config and then point react at that

GRVYDEV commented 3 years ago

While I'm not sure of the best solution, I would also like some way to change the port number as well, since I'm using port 8085 in my setup.

Yup the plan is to have the config change the entire url including port. It sounds like we may mount a custom config and then point react at that

also I will add support for changing the websocket host URL as well in Lightspeed-webrtc once I get this docker stuff finished

Woodham commented 3 years ago

I do like that idea too - providing a config.json via docker volume - but that also seems pretty heavyweight for just a url change.

I think it's a fairly common pattern - mounting config files into docker containers. While it's just a url/port change now it also allows further config easily later :)

sabjorn commented 3 years ago

I do like that idea too - providing a config.json via docker volume - but that also seems pretty heavyweight for just a url change.

It's probably not a bad idea to have a config file for this project in general. There does seem to be enough state to require some way of configuring the project (other than using sed) and a single config (volumed read-only into each container) would solve this problem.

Alternatively, as someone mentioned above ENV variables are not a bad idea either. There are a lot of containers which use this.

This is fairly common, for example the official NGINX image uses:

  environment:
   - NGINX_HOST=foobar.com
   - NGINX_PORT=80

although I think this might actually just be a feature of NGINX and not something handles by docker...

If having configuration within the code isn't desired, the entrypoint of each container is also a good place to handle this (obviously, as long as these are runtime configurations. I haven't dug deep enough to see if this is the case).

GRVYDEV commented 3 years ago

I do like that idea too - providing a config.json via docker volume - but that also seems pretty heavyweight for just a url change.

I think it's a fairly common pattern - mounting config files into docker containers. While it's just a url/port change now it also allows further config easily later :)

Specifically in regards to the react site I know for SURE there will be more config in the future. Especially in respect to monetization when we get to that point (think stripe api key etc). In order to keep this project as general purpose as possible I want to make sure we make smart configuration decisions. Ideally 1 file for the whole project would be GREAT however I am very new to dockerization so I am all ears.

shish commented 3 years ago

Both "one config.json for the project, mounted into each container" and "config stored in .env, which docker-compose passes into the containers via environment variables" sound good to me (though the first gives you all the JSON data types and structure, while the later only does key/value string pairs, so maybe the first is significantly better...?)

(I'm another person hoping for configurable port numbers as I'm already using 8084 :P)

sabjorn commented 3 years ago

@GRVYDEV okay, so I think this actually just goes deeper in general.

In the short term it likely makes sense to just choose something. Mentioned above:

entrypoint: sed ${VARIABLE} && /entrypoint

This will work and is very easy to configure. It allows you to pass in the variables via an .env file and also set them by passing them in when running docker-compose

here are some references:

essentially, these variables end up being used as "build-args" which can modify the the container during build time.


Now, let's ignore docker for a second because it can basically be thought of as any deployment environment.

So, for your future config needs, I would ignore docker and focus on doing what is right for the project elements. A config file is probably the best idea. But really, spend some time thinking about an extensible method that fits your needs.

k3d3 commented 3 years ago

Hmm, after thinking about it, I definitely agree that a mounted config file is the way to go. Especially with docker-compose, those are very simple to make.

Within docker-compose.yml, you'd just add another section under lightspeed_react:

        volumes:
          - ./local/path/to/config.json:/internal/path/to/config.json:ro

The ro keeps it read-only within the container, and can be omitted to make it read-write (though we probably don't want that here).

sabjorn commented 3 years ago

@GRVYDEV there are some things you'll probably want to keep as docker configurable. For example, the port mappings probably make the most sense being configured (via ENV or .env file) and then keeping the project with default port values.

if this is unclear, basically, in the docker-compose.yml you would change the port number before the : to be:

lightspeed-ingest:     
    restart: on-failure
    build: ingest/
    network_mode: host
    ports:
        - "${LIGHTSPEED_PORT}:8084"

oh, additionally, you can also modify the CMD for a container in the docker-compose.yml so, for the WebRTC container, you could do:

lightspeed-webrtc:     
    restart: on-failure
    build: webrtc/
    network_mode: host
    ports:
        - "8080:8080"
        - "65535:65535/udp"
    command: ["lightspeed-webrtc", "--addr=${IP_ADDRESS}"]
sabjorn commented 3 years ago

Hmmmmm, looking deeper, looks like everything could be setup as a runtime variable configured in docker-compose. The only thing standing out is: wsUrl.js

but this file could have an expression which checks for an ENV, and if that isn't set, sets to default

GRVYDEV commented 3 years ago

Hmmmmm, looking deeper, looks like everything could be setup as a runtime variable configured in docker-compose. The only thing standing out is: wsUrl.js

but this file could have an expression which checks for an ENV, and if that isn't set, sets to default

wsURL.js is no longer used. Instead there is a config.json file in the build folder

qdm12 commented 3 years ago

How about a shell entrypoint. Just make it sed things from env variables and make it call serve... at the end. That solves a lot of it I think and is extensible.

Ideally a statically compiled program to do the shell script role AND the HTTP serving (that is, unless you do server side rendering) would be best. But you can do that later.

sabjorn commented 3 years ago

How about a shell entrypoint. Just make it sed things from env variables and make it call serve... at the end. That solves a lot of it I think and is extensible.

Ideally a statically compiled program to do the shell script role AND the HTTP serving (that is, unless you do server side rendering) would be best. But you can do that later.

ENTRYPOINT would likely be the best place for this script to run from.

Checkout the NGINX entrypoint.

The exec "$@" at the end is particularly useful because it allows the CMD to be run after the entrypoint script has finished.

GRVYDEV commented 3 years ago

How about a shell entrypoint. Just make it sed things from env variables and make it call serve... at the end. That solves a lot of it I think and is extensible.

Ideally a statically compiled program to do the shell script role AND the HTTP serving (that is, unless you do server side rendering) would be best. But you can do that later.

While that is a quick fix we will be dealing with a lot of configuration values in the future so I want to be future oriented with this decision

qdm12 commented 3 years ago

ENTRYPOINT would likely be the best place for this script to run from.

Yes plus you could convert environment variables to flags to serve as well for convenience.

While that is a quick fix we will be dealing with a lot of configuration values in the future so I want to be future oriented with this decision

A shell entrypoint is quick to do but would be the best way I'd say, wether it's common practice or for ease of use for end users. The entrypoint can always be changed later to something more robust like rust or go without breaking changes.

I'll try to make a PR with a /bin/sh script for Alpine (who needs bash right)

qdm12 commented 3 years ago

I opened https://github.com/GRVYDEV/Lightspeed-react/pull/10 with such entrypoint and a few tiny Dockerfile improvements. There is still a few questions to iron out and documentation to add though. Anyone, feel free to criticize 😉

GRVYDEV commented 3 years ago

I opened GRVYDEV/Lightspeed-react#10 with such entrypoint and a few tiny Dockerfile improvements. There is still a few questions to iron out and documentation to add though. Anyone, feel free to criticize wink

I am a Docker noob so I may be missing the point of the entrypoint script but this looks like I should be able to pass it a WEBSOCKET_HOST env var and that will automatically replace it?

qdm12 commented 3 years ago

You need to replace the value at runtime, not build time. That way the user doesn't have to build the image himself. To replace it you need a server side entrypoint of some sort, like a shell script. Although for now there is no HTTP server in the image, I think it should be part of the image and entrypoint.

simon-ebner commented 3 years ago

After having prepared Docker images we should also think of a K8S HELM package for Kubernetes (K8S).

brettwilcox commented 3 years ago

For the config file, can we consider TOML? It's so much easier to read and work with.

qdm12 commented 3 years ago

For the config file, can we consider TOML

Agreed, but the configuration is in JSON for the React project (native format). And I'd think environment variables are easier to use than a configuration file at least for Docker (except for secrets where we should use files).

GRVYDEV commented 3 years ago

Closed with #34