# MovieNight: a single Go binary that serves a watch party

> MovieNight is a self-hosted streaming server written in Go that ingests an RTMP feed, repackages it as HLS, and puts a websocket chat room on the same origin. It suits small groups who want one container instead of a media platform, and it is not built for public or multi-tenant streaming.

**zorchenhimer/MovieNight** — Single instance video streaming server with integrated chat.

- Repository: https://github.com/zorchenhimer/MovieNight
- Website: https://discord.gg/F2VSgjJ
- Stars: 727 · Forks: 78
- Language: Go
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/zorchenhimer-movienight

## A watch party on one box instead of a media platform

MovieNight started for a narrow reason. The project describes itself as a single-instance streaming server with chat, written to replace Rabbit as the platform for watching movies with a group of people online. That origin explains most of the design: one process, one port for the web interface, one port for the incoming stream, and a chat panel that anyone in the room can see.

The intended audience is a group of friends or a small team streaming something together, not a broadcast operation. A host runs the server, points OBS at the RTMP port, and everyone else opens the web interface in a browser. There is no transcoding farm, no CDN, no per-viewer session accounting, and no account system beyond a room PIN and a stream key in the configuration file.

That narrowness is the trade. In exchange for giving up scale and audience features, you get something you can run on a home server or a NAS, and the whole thing is a Go binary with a JSON configuration file. The repository is MIT licensed, and the code sits in one flat package with a common/ directory, plus files such as hls.go, chatroom.go, chatcommands.go and stats.go at the top level, which makes it easy to read end to end before you deploy it.

## RTMP in, HLS out, chat over the same origin

The streaming path is the standard one. OBS publishes RTMP to the server, and the server repackages the feed as HLS so browsers can play it. The go.mod file shows the pieces doing that work: github.com/nareix/joy4 handles the media protocol side, and github.com/Eyevinn/hls-m3u8 builds the HLS playlist. The Docker image exposes port 1935 for the RTMP ingest and port 8089 for the web interface, and the compose file maps both to the same numbers on the host.

Chat rides on github.com/gorilla/websocket, and github.com/gorilla/sessions handles session state, which is how the room PIN and the login state survive a page reload. Chat commands live in their own file, chatcommands.go, separate from the room logic in chatroom.go, so commands and message handling are not tangled together.

The client side has two parts worth knowing about. The topics list mentions webassembly, and the repository carries a files/ directory and a static/ directory, so the chat client is served as static assets alongside the page. There is also github.com/playwright-community/playwright-go in go.mod together with ui_test.go, integration_test.go and session_timeout_test.go, which means the browser-facing behaviour is covered by automated browser tests rather than only by unit tests.

## The settings file is the whole control surface

There is no database and no admin panel for configuration. Everything lives in a JSON file, and the shipped settings_example.json is the reference. The Clever-Cloud deployment notes in the project documentation show the full key set as a single JSON object, and the names make the feature set easy to read.

Access control is two keys. `RoomAccess` selects the access mode and `RoomAccessPin` holds the code, while `NewPin` generates a fresh one and `RegenAdminPass` regenerates the administrator password. `StreamKey` is the secret that OBS must present to publish. Rate limiting is granular rather than a single knob: `RateLimitChat`, `RateLimitAuth`, `RateLimitNick`, `RateLimitColor` and `RateLimitDuplicate` each cap a different action, and `MaxMessageCount` bounds the backlog a client receives.

The remaining keys cover moderation and presentation: `Bans` holds blocked users, `ApprovedEmotes` and `WrappedEmotesOnly` control which emotes are usable, `LetThemLurk` toggles the lurk mode, `PageTitle` sets the browser title, `TitleLength` caps how long a title can be, `ListenAddress` and `RtmpListenAddress` set the two listening sockets, and `LogFile`, `LogLevel`, `NoCache` and `StreamStats` control runtime behaviour. `StreamStats` and the stats.go file together give the host a view of the running stream.

One operational detail catches people out. The project notes that the container must be restarted to apply changes made to settings.json, so a live edit to a rate limit or a PIN does not take effect until the process restarts.

## Compiling from source with a Makefile that targets old platforms

Building by hand needs Go 1.16 or newer and GNU Make, and the sequence is short. The documented commands are:

```bash
$ git clone https://github.com/zorchenhimer/MovieNight
$ cd MovieNight
$ (make|gmake) TARGET=windows ARCH=386
$ ./MovieNight
```

The Makefile reads TARGET and ARCH to set GOOS and GOARCH, and it lists a wide set of supported pairs, from 386 and amd64 through arm, arm64, mips and s390x to plan9, nacl and solaris. The `all` target runs fmt, vet, test, then builds the binary and copies settings_example.json to settings.json, so a plain `make` gives you a checked build plus a local configuration file. The `test` target runs the Go test suite across all packages, and `docker` chains fmt and vet with the build.

The Makefile also supports a `GO_VERSION` variable. If your system Go is older than the version you want, setting that variable makes the Makefile call `go1.16.1 build` instead of `go build`. The go.mod file in the repository is newer than the stated build requirement: it declares go 1.21 with a toolchain line pinning go1.24.7, so a 1.16-era toolchain will not build the current source even though the build requirements section still says 1.16 or newer. That gap is worth checking before you start.

## Running the container on ports 8089 and 1935

A published image is available for the master branch:

```bash
docker pull ghcr.io/zorchenhimer/movienight:master
```

If you would rather build it, the repository ships a Dockerfile that compiles in a golang stage and copies the binary into a photon base image:

```bash
docker build -t movienight .
```

The container is started with the web port and the RTMP port published, and there are two documented variants:

```bash
# with default settings file (this uses the settings_example.json config file)
docker run -d -p 8089:8089 -p 1935:1935 movienight

# using a custom settings file
docker run -d -p 8089:8089 -p 1935:1935 -v ./settings.json:/data/config/settings.json movienight
```

The Dockerfile reveals the internal layout. The binary lives at /app/MovieNight, /data is declared as a volume, and /data/emotes and /data/static are created at build time. The example configuration is copied to /data/config/settings.json on first run, and the command line passes `--config`, `--static` and `--emotes` to point the process at those paths.

The compose route is the one to prefer for anything persistent. The compose file in the repository builds the image itself, maps 8089 and 1935, and mounts a named volume called movienight-data at /data, so the configuration survives a container replacement:

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

The project notes that compose creates the movienight-config volume and adds the standard settings.json to it, and again that the container has to be restarted for settings changes to take effect.

## How this differs from pushing RTMP to an external ingest service

The common alternative for a watch party is to keep OBS pointed at somebody else's ingest endpoint and play the result from a hosted player, adding a separate chat service next to it. The approach differs in where the work happens. MovieNight terminates the RTMP connection itself, serves the HLS playlist from its own process, and hosts the websocket chat on the same origin as the page, so the video and the chat share one deployment, one configuration file and one restart cycle.

The gain is operational simplicity. One container, one volume, one JSON file, and the chat does not need its own service, its own auth and its own deployment story. The host also sees the stream statistics from the same process, and moderation lives in the same Bans list that the chat server reads.

The cost is equally clear. There is a single instance, so one machine serves every viewer, and the HLS playlist is written by a library running inside that same binary. There is no published release history for the project, so upgrades are not a versioned operation; you track the master branch or build from source, which means the last push on 2026-08-24 is the only dated marker of where the code stands. Anyone who wants a public channel, an audience of thousands, or uptime across machines has to look elsewhere.

## Where MovieNight is the wrong tool

Three limits are visible in the repository itself. The first is scale, and it is architectural rather than fixable by configuration. A single Go process takes the RTMP feed, writes the HLS playlist and holds the chat connections, so every viewer costs that process. For a group of a dozen people on a home network that is fine. For a public event it is not, and nothing in the configuration adds a second instance.

The second is the release situation. The repository has no GitHub releases, and the only published container tag is master. That means no changelog to read before an upgrade, no version pinning to fall back on, and no stated support commitment. A team that wants to know what changed between two deployments has to diff commits itself.

The third is the security surface, which is small but not empty. The example configuration that ships with the project contains a room PIN of 9999 and a placeholder stream key, and both are visible to anyone who reads the repository. Rotating them is trivial, but nothing forces it, and a deployment that skips that step is a room anyone on the network can join. The chat also accepts commands from participants, and a command surface is exactly the kind of thing that needs watching over time.

For a small group on trusted hardware, none of this is a dealbreaker. For a public or business use, the combination of a single instance, no releases and shared default secrets makes this a poor foundation.

## Conclusion

MovieNight suits a small group that wants to stream a film or event together on its own hardware, with a PIN on the room and chat next to the video, and that can live with a single instance and no published releases. It is the wrong choice for public channels, multi-tenant streaming, or anything needing an ingest service that scales on its own. Before adopting it, build the container and check that OBS can publish to port 1935 on your network, confirm the keys in your settings.json match what your group needs, and decide who holds the room PIN and the stream key, since both default to public values in the shipped example configuration.

## FAQ

### How do I run MovieNight with Docker?

Build it with docker build -t movienight . and start it with docker run -d -p 8089:8089 -p 1935:1935 movienight, or use the compose file with docker-compose up -d, which builds the image and mounts a named volume for the configuration.

### Which ports does MovieNight need?

Port 8089 serves the web interface and port 1935 is the RTMP port that OBS publishes to. The compose file maps both to the same port numbers on the host.

### How do I change the settings in MovieNight?

The configuration is a JSON file, with settings_example.json shipped as the reference. The Docker image keeps it at /data/config/settings.json, and the container has to be restarted for any change to settings.json to take effect.

### Can I compile MovieNight for another platform?

Yes. The Makefile takes TARGET and ARCH, so make TARGET=windows ARCH=386 cross compiles the binary, using gmake on BSD systems. The go.mod file declares go 1.21 with a toolchain line for go1.24.7.

### Does MovieNight publish releases or Docker tags?

The repository has no GitHub releases. The container is published on ghcr as ghcr.io/zorchenhimer/movienight:master, and the last push to the repository was on 2026-08-24.

## Sources

- [Issues](https://github.com/zorchenhimer/MovieNight/issues)
- [License: MIT](https://github.com/zorchenhimer/MovieNight/blob/master/LICENSE)
- [Project website](https://discord.gg/F2VSgjJ)
- [README](https://github.com/zorchenhimer/MovieNight/blob/master/README.md)
- [zorchenhimer/MovieNight on GitHub](https://github.com/zorchenhimer/MovieNight)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/zorchenhimer-movienight
