CI/CD pipeline and test/prod environments #3

Closed
opened 2026-02-26 23:53:03 +01:00 by timothy · 3 comments
Owner

Overview

Set up automated Docker image builds and separate test/prod environments so code changes can be validated before going live.

Depends on: server-management#172 (Gitea Actions runner setup)

1. CI Workflow

.gitea/workflows/build.yml:

  • Trigger on push to main
  • Build Docker image using existing docker/Dockerfile
  • Tag as ersatztv:latest and ersatztv:<commit-sha>
  • Push to container registry (Gitea packages or local registry)
  • Optionally run dotnet test before building

Note: The Dockerfile uses a custom FFmpeg base image from ghcr.io/ersatztv/ersatztv-ffmpeg:7.1.1. We may need to mirror this to our registry or fork it too if GHCR access becomes unreliable.

2. Test Environment

Separate ErsatzTV container on jazz for validating changes before prod:

Prod Test
Container ersatztv ersatztv-test
Port 8409 8410
Config ~/downloadswarm/ersatztv/ ~/downloadswarm/ersatztv-test/
DB ersatztv.sqlite3 ersatztv-test.sqlite3
Image ersatztv:prod ersatztv:latest
Jellyfin Connected (live channels) Not connected

The test instance should:

  • Have its own copy of the config/DB (snapshot from prod initially)
  • NOT be connected to Jellyfin as a tuner (to avoid ghost channels)
  • Share the same media volume mounts (read-only) so it can test playback
  • Be accessible via a different port for manual testing

3. Deployment Flow

code push → Gitea Actions builds image → test container auto-updates
                                        ↓ (manual promotion)
                                        prod container updated

For prod promotion, either:

  • Manual: docker pull ersatztv:latest && docker tag ersatztv:latest ersatztv:prod && docker restart ersatztv
  • Git tag: pushing a v* tag builds and tags as :prod, auto-restarts prod container via webhook/watchtower

4. Open Questions

  • Should we use Watchtower for auto-updates or keep it manual?
  • Do we need a staging Jellyfin tuner pointing at the test instance for full E2E testing?
  • Should the test DB be a periodic copy of prod, or start fresh each time?
## Overview Set up automated Docker image builds and separate test/prod environments so code changes can be validated before going live. Depends on: server-management#172 (Gitea Actions runner setup) ## 1. CI Workflow `.gitea/workflows/build.yml`: - Trigger on push to `main` - Build Docker image using existing `docker/Dockerfile` - Tag as `ersatztv:latest` and `ersatztv:<commit-sha>` - Push to container registry (Gitea packages or local registry) - Optionally run `dotnet test` before building Note: The Dockerfile uses a custom FFmpeg base image from `ghcr.io/ersatztv/ersatztv-ffmpeg:7.1.1`. We may need to mirror this to our registry or fork it too if GHCR access becomes unreliable. ## 2. Test Environment Separate ErsatzTV container on jazz for validating changes before prod: | | Prod | Test | |---|---|---| | Container | `ersatztv` | `ersatztv-test` | | Port | 8409 | 8410 | | Config | `~/downloadswarm/ersatztv/` | `~/downloadswarm/ersatztv-test/` | | DB | `ersatztv.sqlite3` | `ersatztv-test.sqlite3` | | Image | `ersatztv:prod` | `ersatztv:latest` | | Jellyfin | Connected (live channels) | Not connected | The test instance should: - Have its own copy of the config/DB (snapshot from prod initially) - NOT be connected to Jellyfin as a tuner (to avoid ghost channels) - Share the same media volume mounts (read-only) so it can test playback - Be accessible via a different port for manual testing ## 3. Deployment Flow ``` code push → Gitea Actions builds image → test container auto-updates ↓ (manual promotion) prod container updated ``` For prod promotion, either: - **Manual**: `docker pull ersatztv:latest && docker tag ersatztv:latest ersatztv:prod && docker restart ersatztv` - **Git tag**: pushing a `v*` tag builds and tags as `:prod`, auto-restarts prod container via webhook/watchtower ## 4. Open Questions - Should we use Watchtower for auto-updates or keep it manual? - Do we need a staging Jellyfin tuner pointing at the test instance for full E2E testing? - Should the test DB be a periodic copy of prod, or start fresh each time?
timothy added the ci-cdenhancementpriority: medium labels 2026-03-08 23:02:35 +01:00
Author
Owner

Status update — split into the two halves of this issue:

CI workflow — done (#4). .gitea/workflows/docker-build.yml builds/pushes the image and implements the deployment-flow tagging this issue asked for: push to main → :latest (test image), push a v* tag → :prod. Verified end-to-end on main (run #129), image in the Gitea registry, with a test gate + HTTP smoke test.

Test/prod environments — handed to server-management#481. The ersatztv-test (8410) / ersatztv (8409) containers, compose, volumes, registry-pull, Jellyfin isolation for test, and Watchtower-vs-manual promotion are docker-compose/stack config — server-management territory per the project boundaries. Full spec is in server-management#481; prod DB backup-before-deploy is server-management#482.

The three open questions from the description (Watchtower vs manual for prod, staging Jellyfin tuner, test-DB snapshot strategy) are carried into #481.

Leaving this open as the end-to-end tracker — it can close once #481 stands up the containers and the first v* tag cuts a :prod image.

Status update — split into the two halves of this issue: **CI workflow — done (#4).** `.gitea/workflows/docker-build.yml` builds/pushes the image and implements the deployment-flow tagging this issue asked for: push to `main` → `:latest` (test image), push a `v*` tag → `:prod`. Verified end-to-end on `main` (run #129), image in the Gitea registry, with a test gate + HTTP smoke test. **Test/prod environments — handed to server-management#481.** The `ersatztv-test` (8410) / `ersatztv` (8409) containers, compose, volumes, registry-pull, Jellyfin isolation for test, and Watchtower-vs-manual promotion are docker-compose/stack config — server-management territory per the project boundaries. Full spec is in **server-management#481**; prod DB backup-before-deploy is **server-management#482**. The three open questions from the description (Watchtower vs manual for prod, staging Jellyfin tuner, test-DB snapshot strategy) are carried into #481. Leaving this open as the end-to-end tracker — it can close once #481 stands up the containers and the first `v*` tag cuts a `:prod` image.
Author
Owner

Test environment is live and validated (server-management#481). ersatztv-test runs the fork :latest image on 8410 against a one-time prod-DB snapshot, validated end-to-end: 43 channels via API, M3U (43 entries) + XMLTV (80 programmes) render, and VAAPI hardware transcode confirmed (served a real HLS .ts segment). A scoped Watchtower auto-updates test from :latest; prod (8409) is unaffected. Verified independently — both 8410 and 8409 return HTTP 200.

Only the prod cutover remains for full closure: push the first v* tag here → CI cuts the :prod image → server-management switches the ersatztv service from upstream ghcr.io/ersatztv/ersatztv:latest to …/timothy/ersatztv:prod (manual gate, with a pre-cutover DB backup — server-management#482). Low-risk: the fork is upstream 26.3.0 with only CI changes, so it's the same app + ffmpeg version, just our image.

Keeping this and #481 open until the cutover lands.

**Test environment is live and validated** (server-management#481). `ersatztv-test` runs the fork `:latest` image on 8410 against a one-time prod-DB snapshot, validated end-to-end: 43 channels via API, M3U (43 entries) + XMLTV (80 programmes) render, and **VAAPI hardware transcode confirmed** (served a real HLS `.ts` segment). A scoped Watchtower auto-updates test from `:latest`; prod (8409) is unaffected. Verified independently — both 8410 and 8409 return HTTP 200. **Only the prod cutover remains** for full closure: push the first `v*` tag here → CI cuts the `:prod` image → server-management switches the `ersatztv` service from upstream `ghcr.io/ersatztv/ersatztv:latest` to `…/timothy/ersatztv:prod` (manual gate, with a pre-cutover DB backup — server-management#482). Low-risk: the fork is upstream **26.3.0** with only CI changes, so it's the same app + ffmpeg version, just our image. Keeping this and #481 open until the cutover lands.
Author
Owner

Done

What was done: End-to-end CI/CD and test/prod environment work is complete.

  • CI workflow: done (main builds :latest; v* tags build :prod and version/sha tags).
  • Test environment: done and validated (ersatztv-test on 8410, fork :latest, scoped Watchtower, isolated from Jellyfin, M3U/XMLTV/API/transcode checks validated).
  • Prod cutover: done via server-management#481; prod now runs 192.168.1.95:3000/timothy/ersatztv:prod at v26.3.1.
  • Backup-before-deploy: done via server-management#482; deploy wrapper takes a backup first and restore was tested.

Root cause: n/a.

Files changed: none in this closure step.

Verification: Reviewed this issue's prior comments plus server-management#481 and server-management#482; the only remaining blocker here was prod cutover, and #481/#482 document that cutover, validation, backup gate, and restore safety are complete.

Deferred: GitOps replacement for Compose deployment remains separately tracked in server-management#483.

Follow-up issues: server-management#483.

Docs updated: Already updated in the referenced server-management#481 and server-management#482 close comments.

## Done **What was done**: End-to-end CI/CD and test/prod environment work is complete. - CI workflow: done (`main` builds `:latest`; `v*` tags build `:prod` and version/sha tags). - Test environment: done and validated (`ersatztv-test` on 8410, fork `:latest`, scoped Watchtower, isolated from Jellyfin, M3U/XMLTV/API/transcode checks validated). - Prod cutover: done via `server-management#481`; prod now runs `192.168.1.95:3000/timothy/ersatztv:prod` at v26.3.1. - Backup-before-deploy: done via `server-management#482`; deploy wrapper takes a backup first and restore was tested. **Root cause**: n/a. **Files changed**: none in this closure step. **Verification**: Reviewed this issue's prior comments plus `server-management#481` and `server-management#482`; the only remaining blocker here was prod cutover, and #481/#482 document that cutover, validation, backup gate, and restore safety are complete. **Deferred**: GitOps replacement for Compose deployment remains separately tracked in `server-management#483`. **Follow-up issues**: `server-management#483`. **Docs updated**: Already updated in the referenced `server-management#481` and `server-management#482` close comments.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: timothy/ersatztv#3