25 min read

Install Halcyon Video and Access It with Localtonet

Install and verify Halcyon Video, connect it to Jellyfin or Plex, then access its self-hosted web app remotely with a Localtonet HTTP tunnel.

Self-Hosting · Halcyon Video · Docker · Localtonet · 2026

Deploy a version-conscious media frontend and publish only the endpoint you intend to share

Halcyon Video presents a personal media library as a walkable video rental store. This guide separates the published v0.9.1 Docker image from the current master interface, which presents Jellyfin, Plex, and Emby as primary providers. We cover Docker deployment, loopback-only networking, local verification, updates, and remote access with Localtonet. Loading the public interface is only the first checkpoint: library access, artwork, authentication, and playback must also work from the remote browser.

📦 Separate release and development behavior 🔒 Prefer a loopback-only Docker publication 🌐 Verify local and off-LAN access independently
Home server hosting Halcyon Video with a Localtonet connection to a remote phone
Publishing the Halcyon endpoint provides access to that endpoint, not automatic access to every media-service address the application uses.

Separate v0.9.1 from the current master interface

The v0.9.1 release page explicitly publishes ghcr.io/halcyon-video/halcyon-video:v0.9.1 for amd64 and arm64. It also mentions :latest, but that floating tag is not a permanent synonym for v0.9.1. Use an explicit version for a controlled deployment, and retain the image digest when stronger reproducibility is important.

The release-page snapshot reviewed for this revision reports 619 commits to master since v0.9.1. That is a point-in-time comparison, not a number that will remain current. The supplied repository snapshot identifies master commit 8c5f8f22fc840068a6179c097c1865c5790dd24f. Neither the current master tree nor the project's live browser application should be treated as documentation of every feature in the older image.

Version choiceWhat is establishedHow to use it here
v0.9.1 imageThe release publishes the named image for amd64 and arm64.The primary deployment example uses this tag. Verify its listener and interface before assuming newer features exist.
Current master and live interfaceThe current interface presents Jellyfin, Plex, Emby, optional request-service fields, Romm fields, and a demo entry point.The configuration descriptions below are labeled as current-interface behavior, not a v0.9.1 feature guarantee.
:latestA movable image tag mentioned by the release.Do not use it as a substitute for recording the exact image you tested.
Verification boundaries in this guide

The supplied current evidence does not include the contents of the tagged Dockerfile, tagged Compose file, or integration implementation files. We therefore include commands to inspect those files before deployment and qualify request-routing details rather than claiming a fresh source-code verification. Likewise, the current interface establishes visible configuration choices, not when each choice first appeared in a release.

Understand what the tunnel does and does not publish

Halcyon Video is the media-library presentation layer in this workflow. Its current interface describes the primary media server as the provider for cataloging movies and retrieving poster artwork. Keep library management, media authorization, and media delivery responsibilities separate from the Halcyon web endpoint.

The existing project description identifies a Vite, TypeScript, and three.js browser experience, with desktop and HTPC-related deployment paths. This tutorial covers the Docker-hosted browser endpoint only. It does not establish that desktop wrappers, Remote Play, or other platform-specific paths have identical networking behavior in v0.9.1 and current master.

📦Docker endpointThe host publication makes a container listener reachable at a selected host address and port.
🖥️Viewer's browserThe browser loads the interface and enforces origin, HTTPS, graphics, and media compatibility rules.
🎬Media providerThe current interface offers Jellyfin, Plex, and Emby. Each remains a separate service and authorization boundary.
🔗Localtonet HTTP tunnelOur platform forwards public requests to the configured local target. It does not automatically publish unrelated dependencies.

Verify request origins instead of assuming them

A browser frontend can contact a media server directly, call a same-origin backend, or use a combination of paths. The current interface alone does not establish which component originates every catalog, authentication, artwork, playback, or optional-integration request. Do not assume that the container proxies all those requests, and do not assume that every integration is entirely browser-direct.

For your deployed version, open browser developer tools, select the Network panel, and exercise one operation at a time. Inspect the request URL, initiator, redirects, and response status. A request to the configured media-server hostname proves that the browser needs a route to that destination. A same-origin request requires further inspection of the application or server configuration before drawing conclusions about its upstream route.

For example, if the browser requests http://192.168.1.20:8096, that address may work on the home LAN but usually cannot be reached over cellular data. A public Halcyon URL does not make that private destination routable. Similarly, an HTTPS page can have insecure HTTP requests blocked as mixed content, while cross-origin API calls can fail because the destination does not permit the page's origin.

Interface loading is not proof of remote playback

Test the interface, catalog, artwork, and playback separately. Any browser-direct dependency needs its own secure, authenticated route from the viewer's network. Do not disable browser security, weaken media authentication, or publish an administration service merely to make a test pass.

Optional integrations and Remote Play

The current live interface explicitly describes Jellyseerr or Overseerr as enabling a “Coming Soon” requests wall. It says unowned-title cover art is fetched from TMDB's image CDN, with the request server returning the artwork address, while owned-media artwork is served by the media server. This establishes an additional artwork destination, but not the origin of every integration API request.

The same interface offers Romm server and credential fields for game and ROM shelves. It does not establish all proxying, credential-storage, or transport details. Inspect the matching revision before giving such an integration sensitive credentials. Remote Play is also a separate project workflow; do not equate a public HTTP endpoint with streaming an existing store session or assume any particular TURN-relay behavior from the evidence available here.

Prepare the host and choose the exposure boundary

Use a Docker host you administer, a supported host architecture, and a modern browser with working graphics acceleration. For the optional repository workflow, install Git and the Docker Compose plugin. Docker installation itself depends on the operating system and is a prerequisite here; first confirm that the engine is running and your account can use it.

docker version
docker compose version
git --version

The Docker command should report a reachable server, not just an installed client. Compose and Git are needed only for the repository inspection or Compose path. Resolve daemon or permission errors before attempting application deployment.

For personal-media testing, prepare an already functioning media server and a least-privilege viewing account. On current master, the interface offers Jellyfin, Plex, and Emby; on v0.9.1, use only the choices actually present in the pinned application. Keep provider credentials out of commands, screenshots, issue reports, and public tunnel descriptions.

The earlier project description warned that the full roughly 2,000-title synthetic demo could consume several gigabytes of browser memory and substantial GPU resources, and described a lighter 2.5D mode. Those details have not been independently established for both versions in the supplied current evidence. Treat them as capacity-planning cautions, not fixed requirements or guaranteed v0.9.1 controls. If your installed build offers a lighter mode, it is useful for separating graphics problems from networking failures.

Prefer the client and container on the same host

For the simplest controlled topology, run our client directly on the Docker host and publish Halcyon on 127.0.0.1. The Localtonet target will be 127.0.0.1:8080. Ordinary LAN clients should not be able to reach that publication directly.

If our client must run on a different LAN device, loopback publication will not work for it. Intentionally bind to a reachable host LAN address, permit only the necessary client-to-host traffic, and account for network segmentation. A client running in another container has a separate network namespace; its 127.0.0.1 is not the Docker host.

Inspect and install the Docker deployment

Docker deployment starting a Halcyon Video container and publishing its web listener
Inspect the selected revision before starting it, and explicitly choose the host address used for the published port.

Check the tagged files first

The original deployment used container port 80. Because the tagged file contents are not included in the current evidence, verify that listener before running the example below. These commands retrieve the repository and display the selected release's files without starting its services:

git clone https://github.com/halcyon-video/halcyon-video.git
cd halcyon-video
git checkout --detach v0.9.1
git rev-parse HEAD
git show HEAD:Dockerfile
git show HEAD:docker-compose.yml
docker compose config

Read the Dockerfile's final stage, startup configuration, and any referenced web-server files. An EXPOSE instruction is metadata, not proof that a process actually listens. Check the Compose image, build, ports, volumes, and command settings. A tagged repository checkout does not pin an image if its Compose file still references a floating tag.

Option A: run the published v0.9.1 image

After confirming that the image serves HTTP on container port 80, use the following standalone deployment. If the selected image's listener differs, substitute the verified container port rather than blindly retaining 80.

docker pull ghcr.io/halcyon-video/halcyon-video:v0.9.1

docker image inspect \
  --format '{{json .Config.ExposedPorts}}' \
  ghcr.io/halcyon-video/halcyon-video:v0.9.1

docker run -d \
  --name halcyon-video \
  --restart unless-stopped \
  -p 127.0.0.1:8080:80 \
  ghcr.io/halcyon-video/halcyon-video:v0.9.1

docker ps --filter name=halcyon-video
docker port halcyon-video
docker logs --tail 100 halcyon-video

Open http://127.0.0.1:8080 on the Docker host. The expected publication is 127.0.0.1:8080 forwarding to the confirmed container listener. The restart policy restarts the container as appropriate after Docker restarts, except when it has been intentionally stopped. It does not start the Localtonet client or tunnel.

If host port 8080 is occupied, use -p 127.0.0.1:8090:80 and change the browser and tunnel target to port 8090. The right-hand port remains the verified container listener.

Docker publication is a separate security decision

-p 8080:80 publishes on all host interfaces by default, not only to Localtonet. Prefer -p 127.0.0.1:8080:80 for a client running directly on the host. Broader publication can expose the application to LAN or other reachable networks independently of whether the tunnel is running. Docker's firewall integration can also differ from ordinary host-service filtering, so verify actual reachability rather than relying only on a firewall GUI.

When a LAN binding is intentional

For a separate Localtonet client device, replace the loopback address in the publication with the Docker host's actual LAN IP. Restrict access using approved firewall and network policy, ideally allowing only the client device and any explicitly authorized local viewers. Do not use an example private address without replacing it with your host's address.

Test from the client device before creating the tunnel. Also test from a device that should be denied. Stopping the tunnel does not close a LAN-published Docker port; stop the container or change its publication when that route is no longer required.

Option B: use the repository Compose configuration

Use Compose instead of the standalone container, not alongside it on the same host port. Inspect the effective model, then edit the selected service's publication to loopback for the same-host topology. Re-run docker compose config and verify that no additional all-interface publication remains.

Choose the startup operation based on the actual file. docker compose pull downloads images; it does not build source code. A service with a build definition may need docker compose build or docker compose up -d --build. Do not assume that checking out master and pulling an image deploys that source revision.

# Inspect before selecting a pull or build workflow.
docker compose config

# For a verified image-based configuration:
docker compose pull
docker compose up -d

# For a verified source-build configuration, use instead:
# docker compose up -d --build

docker compose ps
docker compose logs --tail 100

For current master, use a separate checkout or deliberately switch branches, record git rev-parse HEAD, and inspect its files again. Exact master startup behavior is conditional on that revision's Compose and Dockerfile contents, not inherited from v0.9.1.

Configure the media provider supported by your version

Halcyon Video connecting to separate Jellyfin and Plex media-server examples on a private network
This existing illustration shows Jellyfin and Plex examples. The current interface also presents Emby; it is not a two-provider-only architecture.

The following field descriptions come from the current live interface associated with the project's current development experience. They are not a claim that v0.9.1 has identical labels or integrations. In the pinned image, inspect its connection screen and use only the controls it actually provides.

Jellyfin and Emby in the current interface

The current “Media Library Sign In” screen presents a Server Type selector with Jellyfin, Plex, and Emby, along with Server Address, Username, and Password fields. For a server-address workflow, enter the intended provider's reachable URL and a restricted media account, then use “Connect & Sync.” Check that the catalog and artwork correspond to the expected account.

Do not infer identical authentication internals or playback compatibility between Jellyfin and Emby from their shared form. Verify each provider you intend to use, including logout, rejected credentials, and an authorized playback attempt. Adding Emby to this current-interface description does not establish that Emby was available in v0.9.1.

Plex in the current interface

The current interface describes a plex.tv code-based workflow: request a sign-in code, enter it at plex.tv/link on an authorized device, then select the available servers. It permits selecting one or more servers. Follow the instructions displayed by your actual deployment if the pinned image differs.

Successful account linking proves authorization, not universal network reachability. Check the selected server and final media URL from the intended viewer's network. A private server address can still fail after a successful sign-in.

Demo and optional services

The current interface offers “Try Demo Store (No Server Required).” Use it to isolate application loading and graphics from personal-media dependencies. If the pinned image provides a demo, use it for the same diagnostic purpose without assuming its catalog size or behavior matches master.

Current-interface Jellyseerr/Overseerr and Romm fields are optional. Leave them unconfigured until the primary media workflow passes. Adding one integration at a time makes failures attributable and avoids unnecessarily distributing credentials. Review its permissions and actual request destinations before exposing the combined workflow.

Check the access-control boundary

Do not treat an obscure public URL as authentication or assume that Halcyon is an independent gateway protecting every media service. Open a fresh private browsing session and inspect what an unauthenticated visitor can see. Media authorization must remain enforced by the relevant service, regardless of whether the Halcyon interface itself is public.

A local URL and a public HTTPS URL are different browser origins. Settings or authorization stored per origin may not carry over, so a new sign-in can be expected without indicating a tunnel failure. The evidence here does not establish exactly where each Halcyon revision stores credentials; inspect that behavior before sharing a configured browser or device.

Verify locally before adding remote access

Work outward from the container. Keep a short record of the image tag or commit, publication, provider, browser, and test result. Do not include tokens or passwords in that record.

  1. Check container state. Confirm that it remains running and that logs do not show a restart loop.
  2. Check the publication. Confirm the intended loopback or LAN address, not merely the host port.
  3. Open the interface from the client host. For the preferred same-host setup, use http://127.0.0.1:8080.
  4. Test the demo if available. Check assets, rendering, navigation, and browser stability.
  5. Connect a personal library. Verify synchronization, posters, detail views, and playback of an authorized title.
  6. Inspect failed requests. Identify the actual destination before changing Docker, media-server, or tunnel settings.
docker inspect -f '{{.State.Status}}' halcyon-video
docker port halcyon-video
docker logs --tail 100 halcyon-video
curl -I http://127.0.0.1:8080/

An HTTP response confirms a listener responded, not that the full application works. If a HEAD request is unsupported, use the browser or a normal GET. Test graphics and playback in the actual browser intended for viewing.

With loopback-only publication, a second LAN device should not connect to the host's port 8080. That is the desired isolation result. Only require successful direct LAN access when you deliberately selected a LAN binding, especially when our client runs on another device.

Publish the verified endpoint with Localtonet

Public HTTPS request passing through a Localtonet relay to the Halcyon Docker host
For a client running directly on the Docker host, the preferred local target is the verified loopback publication.

Our client establishes an outbound connection to a Localtonet relay. Standard tunneling does not require inbound router port forwarding, a public IP address, or VPN setup. It also does not require opening an inbound firewall port for the public tunnel. This does not override organizational policy or remove the separate exposure consequences of Docker's published ports.

Client installation and dashboard evidence limitation

The supplied material does not include a verified operating-system-specific installer command, download URL, or the current HTTP documentation's exact field-by-field sequence. Begin at our official documentation and HTTP tunnel setup guide for the current installation path and screen order. We do not substitute an invented shell command or present the operational checklist below as a verbatim dashboard procedure.

Create an account if needed, install the current client for your operating system using those official instructions, and run it on the chosen device. Authenticate that device with its device-specific token and confirm it is connected. Keep the token private. The remaining values and lifecycle are established by our platform's documented concepts.

1

Run the client where the local endpoint is reachable

Prefer running it directly on the Docker host. Verify http://127.0.0.1:8080 there before proceeding. For another client device, verify the deliberately permitted LAN target instead.

2

Authenticate and identify the client device

Use the device-specific authentication token associated with that client. Confirm that you are configuring the intended connected device, not another machine in your account.

3

Select an available relay

Choose a server or region currently available in the dashboard. Availability can vary; do not reuse a hardcoded server code from an example.

4

Configure the HTTP tunnel using the current screen order

Set the local IP and published host port to the verified target. For this same-host example, those values are 127.0.0.1 and 8080. HTTP Process Type choices are Random Sub Domain, Custom Sub Domain, and Custom Domain, subject to current availability. Consult current DNS instructions before using a custom domain.

5

Start the tunnel and use its public address

Review the target, then press Start. Creating a configuration does not mean it is running. Use the assigned public HTTPS address while the selected client is connected and the tunnel is running.

6

Stop or delete the configuration when no longer needed

Stop public access after the test or viewing session. Delete the tunnel if you no longer need its configuration. Independently remove any temporary media-service access path.

Test from a genuinely different network

Disable Wi-Fi on a phone or use another off-LAN connection. Open the public address in a private browsing session. First test interface loading and the demo if available, then authenticate the intended media workflow. Do not conclude that remote access works based on a browser still connected to the home LAN.

CheckpointSuccess meansIf it fails
Public pageThe intended Halcyon interface and assets load.Check local target, client connection, and tunnel state.
Demo, if availableThe browser renders and navigates the application.Inspect assets, browser graphics, memory, and console errors.
Personal catalogThe authorized library and artwork appear.Inspect provider routing, authentication, CORS, and mixed content.
PlaybackAn authorized item actually starts playing.Inspect the final media URL, redirects, codec support, and provider logs.
Fresh session and logoutA new or logged-out viewer sees only intended access.Review browser persistence and authentication assumptions.

Operate, update, and recover deliberately

Routine standalone operations

docker ps -a --filter name=halcyon-video
docker logs -f --tail 100 halcyon-video
docker restart halcyon-video
docker stop halcyon-video
docker start halcyon-video

Run the operation you need, not the whole block as a sequence. Stopping preserves the container definition. Removing a container requires recreating its intended publication and restart policy, and can discard data stored only in its writable layer. Inspect mounts and persistence before removal.

Record the working deployment before updating

docker inspect -f '{{.Config.Image}}' halcyon-video
docker inspect -f '{{.Image}}' halcyon-video
docker inspect -f '{{json .Mounts}}' halcyon-video
docker image inspect \
  --format '{{json .RepoDigests}}' \
  ghcr.io/halcyon-video/halcyon-video:v0.9.1

Save the current tag, image identity, configuration, and any required persistent data securely. Read the target release notes and verify its listener. Stop the tunnel during the update. The following example assumes the new release still serves on container port 80; replace NEW_TAG with a real reviewed tag before execution.

docker pull ghcr.io/halcyon-video/halcyon-video:NEW_TAG
docker stop halcyon-video
docker rm halcyon-video
docker run -d \
  --name halcyon-video \
  --restart unless-stopped \
  -p 127.0.0.1:8080:80 \
  ghcr.io/halcyon-video/halcyon-video:NEW_TAG

Repeat local and off-LAN checks before sharing the endpoint again. For rollback, recreate the last known-good image with its matching configuration and preserved data. Returning to v0.9.1 is appropriate only if it was your tested baseline. Container rollback does not reverse browser-storage changes, media-server upgrades, DNS changes, or credentials.

Compose and master operations

docker compose ps
docker compose logs -f --tail 100
docker compose restart
docker compose stop
docker compose up -d

docker compose down removes the project's containers and networks, so use it deliberately. Avoid adding volume-removal options unless you intend to delete that data. Preserve local Compose edits, especially the loopback publication.

For master updates, record the previous commit, review changes, and inspect the effective configuration again. Rebuild a source-built service; pull an image-based service only when its image reference is the intended release. Checking out an older commit does not roll back a floating image automatically. Record both source and image identities when both affect the deployment.

Troubleshoot one boundary at a time

Diagnostic sequence checking the Halcyon container, media dependency, tunnel, and remote browser
Find the first failing checkpoint before changing settings in a different layer.

The container exits or the port is already allocated

Inspect docker ps -a, recent logs, and the publication. Check the image architecture, startup command, container-name conflict, and occupied host ports. v0.9.1 publishes amd64 and arm64 variants; do not assume another architecture is supported. If 8080 is occupied, recreate the container with 127.0.0.1:8090:80 after verifying port 80, and update the tunnel target.

Localhost works, but the LAN address does not

With the preferred loopback publication, that is expected. Do not widen it simply to satisfy an unnecessary LAN test. If our client is on another device, verify an intentional LAN binding and approved firewall rules. Ensure the client uses the Docker host's LAN address, not its own localhost.

The interface loads, but a provider fails

In current master this may involve Jellyfin, Plex, or Emby; in v0.9.1 troubleshoot the provider actually available and configured. Inspect the exact failed request. Check DNS, route, scheme, port, authentication, and redirects. Opening the provider URL directly in the same browser tests basic reachability, but does not prove that cross-origin API calls will be allowed.

Resolve CORS and preflight failures at the appropriate service or proxy. Resolve mixed content with a properly authenticated HTTPS endpoint when the browser must contact that service directly. Do not disable browser security or assume the Halcyon tunnel rewrites unrelated absolute URLs.

Artwork succeeds but playback fails

Catalog and media delivery can follow different paths. Inspect the final stream URL and response, not just the initial library call. Check browser codec support and any session or WebSocket requests actually observed. Test the same item through the provider's normal interface from the same off-LAN device to separate provider-side failures from Halcyon behavior.

The store is blank or slow

Check console errors, graphics acceleration, memory pressure, and browser support. Try another current browser or device. If your installed revision offers 2.5D or another reduced-rendering mode, compare it with the full store. Serving the frontend from an arm64 host does not establish that a Raspberry Pi browser can render every scene smoothly.

The client runs inside another container

Its loopback address belongs to that container, not the host. Use a deliberately designed Docker-network route or move the client directly onto the host for this tutorial's loopback pattern. Do not expose the Docker daemon or weaken unrelated network isolation to solve an address mistake.

The public URL does not open

Check in order: container state, HTTP response from the Localtonet client's network namespace, selected device, relay configuration, target address and port, client connection, and tunnel running state. A created but stopped tunnel cannot serve the endpoint. If the page loads but media fails, return to dependency diagnostics rather than repeatedly recreating the tunnel.

Frequently asked questions

Does Halcyon support Emby?

The current live interface presents Emby alongside Jellyfin and Plex. The supplied evidence does not establish that the pinned v0.9.1 image includes the same provider list, so check that image's interface before relying on Emby there.

Which Docker port should Localtonet target?

Target the published host port reachable from our client. After confirming container port 80, this guide uses 127.0.0.1:8080:80, so a client running directly on the host targets 127.0.0.1 and port 8080.

Should I use v0.9.1, latest, or master?

Use an explicit reviewed release for controlled updates. Both latest and master can move. Record the image digest or source commit and deployment configuration, and do not assume a master checkout determines the image used by Compose.

Does publishing Halcyon also publish my media server?

No. Our HTTP tunnel publishes its configured target. Inspect the deployed application's requests to determine which other destinations must be reachable. Browser-direct media requests require their own secure, authenticated route.

Does loopback publication prevent public access through Localtonet?

No. A client running directly on the Docker host can reach the loopback endpoint and carry requests through the tunnel. Loopback publication prevents ordinary direct LAN access; it does not authenticate visitors using the public URL.

Is this the same as Halcyon Remote Play?

No. This workflow publishes a hosted HTTP endpoint. It does not establish or configure the project's separate Remote Play session-sharing behavior.

Can I use an arm64 or Raspberry Pi host?

The v0.9.1 release lists an arm64 image. Host compatibility and browser rendering performance are separate questions. Verify the operating system and Docker environment, then test graphics and memory requirements on the actual viewing device.

Will the endpoint work after the Localtonet client stops?

No. The selected client must remain connected and the tunnel must be running. Separately, a LAN-published Docker port can remain reachable even after the tunnel stops.

Publish a verified endpoint, not an assumed architecture

Choose the version deliberately, confirm its listener and provider behavior, and prefer loopback-only publication with our client on the Docker host. Once local checks pass, use Localtonet to test the public interface and each authorized media workflow from outside the LAN.

Get Started Free →

Corrections & updates

Substantive changes approved by the Localtonet editorial team are listed transparently below.

Update the v0.9.1 versus master comparison using current repository evidence and remove the obsolete 58-commit count. Scope every Halcyon feature and configuration statement to either v0.9.1 or the current master branch, add Emby where current behavior is discussed, and verify the browser-origin and optional-integration architecture against primary project files. Recheck the tagged Dockerfile and Compose file before retaining port, pull, build, and startup commands. Make loopback-only Docker publication the preferred same-host Localto

Restructure the body to begin with the hero, followed immediately by a clickable article-specific guide card, and remove the forbidden outer article wrapper. Replace the current documentation placeholders with a complete, verified Docker or Docker Compose installation using the repository's current README and Compose file, including acquisition, version choice, configuration, published port, startup, local URL, status and logs, stopping, restarting, updating, and rollback or recovery where documented. Add explicit prerequisites for Do

Localtonet is a secure multi-protocol tunneling and proxy platform designed to expose localhost, devices, private services, and AI agents to the public internet supporting HTTP/HTTPS tunnels, TCP/UDP forwarding, mobile proxy infrastructure, file server publishing, latency-optimized game connectivity, and developer-ready AI agent endpoint exposure from a single unified control plane.

support