{"id":48083904,"url":"https://github.com/marcelo-6/caisson","last_synced_at":"2026-04-04T15:04:29.367Z","repository":{"id":345080942,"uuid":"1183432365","full_name":"marcelo-6/caisson","owner":"marcelo-6","description":"Offline Docker service updater for airgapped edge devices","archived":false,"fork":false,"pushed_at":"2026-03-17T16:15:05.000Z","size":129,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-18T05:15:25.504Z","etag":null,"topics":["airgap","airgapped","cli","docker","iot","updater"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/marcelo-6.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-16T15:48:42.000Z","updated_at":"2026-03-17T16:15:26.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/marcelo-6/caisson","commit_stats":null,"previous_names":["marcelo-6/caisson"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/marcelo-6/caisson","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcelo-6%2Fcaisson","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcelo-6%2Fcaisson/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcelo-6%2Fcaisson/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcelo-6%2Fcaisson/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/marcelo-6","download_url":"https://codeload.github.com/marcelo-6/caisson/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcelo-6%2Fcaisson/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31403952,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T10:20:44.708Z","status":"ssl_error","status_checked_at":"2026-04-04T10:20:06.846Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["airgap","airgapped","cli","docker","iot","updater"],"created_at":"2026-04-04T15:03:52.529Z","updated_at":"2026-04-04T15:04:29.358Z","avatar_url":"https://github.com/marcelo-6.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- markdownlint-disable MD033 --\u003e\n\u003c!-- markdownlint-disable MD041 --\u003e\n\u003cdiv align=\"center\"\u003e\n\n\u003ch4\u003eOffline Docker service updater for airgapped edge devices.\u003c/h4\u003e\n\n\u003ca href=\"https://github.com/marcelo-6/caisson/releases\"\u003e\u003cimg src=\"https://img.shields.io/github/v/release/marcelo-6/caisson?logo=github\" alt=\"GitHub Release\"\u003e\u003c/a\u003e\n\u003ca href=\"https://crates.io/crates/caisson/\"\u003e\u003cimg src=\"https://img.shields.io/crates/v/caisson?logo=Rust\" alt=\"Crate Release\"\u003e\u003c/a\u003e\n\u003ca href=\"https://codecov.io/gh/marcelo-6/caisson\"\u003e\u003cimg src=\"https://codecov.io/gh/marcelo-6/caisson/graph/badge.svg?token=TPJMXTJ5ZQ\u0026amp;logo=Codecov\u0026amp;logoColor=white\" alt=\"Coverage\"\u003e\u003c/a\u003e\n\u003cbr\u003e\n\u003ca href=\"https://github.com/marcelo-6/caisson/actions?query=workflow%3A%22CI%22\"\u003e\u003cimg src=\"https://img.shields.io/github/actions/workflow/status/marcelo-6/caisson/ci.yml?branch=main\u0026amp;logo=GitHub%20Actions\u0026amp;logoColor=white\u0026amp;label=CI\" alt=\"Continuous Integration\"\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/marcelo-6/caisson/actions?query=workflow%3A%22CD%22\"\u003e\u003cimg src=\"https://img.shields.io/github/actions/workflow/status/marcelo-6/caisson/cd.yml?logo=GitHub%20Actions\u0026amp;logoColor=white\u0026amp;label=CD\" alt=\"Continuous Deployment\"\u003e\u003c/a\u003e\n\u003ca href=\"https://docs.rs/caisson/\"\u003e\u003cimg src=\"https://img.shields.io/docsrs/caisson?logo=Rust\u0026amp;logoColor=white\" alt=\"Documentation\"\u003e\u003c/a\u003e\n\n\u003cimg alt=\"Crates.io Total Downloads\" src=\"https://img.shields.io/crates/d/caisson?logo=Rust\"\u003e\n\u003cimg alt=\"GitHub Downloads (all assets, all releases)\" src=\"https://img.shields.io/github/downloads/marcelo-6/caisson/total?logo=GitHub\"\u003e\n\u003cbr\u003e\n\u003cimg alt=\"GitHub commits since latest release\" src=\"https://img.shields.io/github/commits-since/marcelo-6/caisson/latest\"\u003e\n\n\u003c/div\u003e\n\n## Status\n\n\u003e [!NOTE]\n\u003e This project is in early development and not ready for production use.\n\nThe goal of `Caisson` is to provide an update workflow for predefined Docker services on air-gapped or offline devices. In `0.1.0`, operators should only need to point the CLI at a local package file and apply the update. The backend handles validation, image import, service restart, health checks, rollback, and local audit history.\n\n## Why the name\n\nA `caisson` is a controlled chamber used to move work safely through difficult or isolated environments. That is the idea behind this project: a controlled path for getting updates onto an edge device and applying them safely.\n\n## What the project is about\n\n`Caisson` is focused on:\n\n- accept an offline update package\n- work through a CLI-first operator flow in `0.1.0`\n- validate it\n- load the Docker image\n- update a predefined service\n- run health checks\n- roll back automatically if the update fails\n- keep a local history of what happened\n- support predefined services that use either direct Docker control or developer-provided Docker Compose definitions\n\n## What it is not\n\n`Caisson` is not:\n\n- a fleet manager\n- a registry\n- a cloud update system\n- a general Docker dashboard\n\n## Alternatives\n\n[Hauler](https://docs.hauler.dev/docs/airgap-workflow) describes itself as an “Airgap Swiss Army Knife” for fetching, storing, packaging, and distributing artifacts across disconnected environments.\n\n[Mender](https://docs.mender.io/) supports standalone deployments for devices without network connectivity, including updates triggered manually or through external storage, and it also has Docker Compose update support.\n\n[Portainer](https://www.portainer.io/) is a broader container management UI for Docker and other environments, positioned as a toolset for building and managing containers.\n\n`Caisson` exists because I wanted something smaller and more opinionated than those options: a local updater focused specifically on offline Docker service updates on one device, with a simple operator experience. It is not always that the human closest to the end device has the technical knowlege to safely update its services.\n\n## Current direction\n\nThe first release is focused on getting the baseline workflow working:\n\n- offline package intake\n- validation\n- Docker image import\n- service update\n- health checks\n- rollback\n- local audit/history\n\n\u003e [!NOTE]\n\u003e Current roadmap:\n\u003e\n\u003e - `0.1.0`: cli for offline updater\n\u003e - `0.2.0`: minimal GUI on top of the same application logic\n\u003e - `0.3.0`: self-updater support\n\u003e - `0.4.0`: encryption and signatures\n\n## Current CLI\n\n```bash\ncargo run -- service list --services path/to/services.toml --state-dir .caisson-state\n```\n\nThat command shows the predefined services from `services.toml` together with the locally known image state and the last recorded update result.\n\nValidate a package without changing anything:\n\n```bash\ncargo run -- package validate path/to/update.edgepkg --services path/to/services.toml --state-dir .caisson-state\n```\n\nThat command:\n\n- stages the package locally\n- validates the `.edgepkg` tar structure\n- parses `manifest.toml`\n- checks target service compatibility against `services.toml`\n- persists validation records and audit events under the chosen state directory\n\nLoad a package:\n\n```bash\ncargo run -- package load path/to/update.edgepkg --services path/to/services.toml --state-dir .caisson-state\n```\n\nThat command validates the package first, asks for confirmation, imports the staged `image.tar` into Docker, applies the update to the target service, runs health checks, and rolls back automatically if the update does not stay healthy.\n\nIf you want the same flow without the confirmation prompt:\n\n```bash\ncargo run -- package load path/to/update.edgepkg --yes --services path/to/services.toml --state-dir .caisson-state\n```\n\nTo inspect local update history after a run:\n\n```bash\ncargo run -- history list --state-dir .caisson-state\ncargo run -- history show \u003cupdate-id\u003e --state-dir .caisson-state\n```\n\nIf you want to remove leftover local package artifacts created under the updater state directory:\n\n```bash\ncargo run -- package cleanup --state-dir .caisson-state\n```\n\nThat command currently cleans only the local package workspace. It does not remove validation records, audit history, candidate releases, or service state.\n\nQuick way to build a local package for manual testing is:\n\n```bash\ntmpdir=$(mktemp -d)\ndocker pull alpine:3.19\ndocker pull alpine:3.20\ndocker tag alpine:3.19 example/frontend:current\ndocker tag alpine:3.20 example/frontend:1.2.3\ndocker save example/frontend:1.2.3 -o \"$tmpdir/image.tar\"\ncp tests/fixtures/manifests/valid-frontend.toml \"$tmpdir/manifest.toml\"\ntar -cf \"$tmpdir/frontend.edgepkg\" -C \"$tmpdir\" manifest.toml image.tar\n```\n\nIf you want to test the full `package load` path, start the managed service first so there is something to replace:\n\n```bash\ndocker rm -f frontend 2\u003e/dev/null || true\ndocker run -d --name frontend example/frontend:current sh -c 'sleep infinity'\n```\n\nThen run:\n\n```bash\ncargo run -- package load \"$tmpdir/frontend.edgepkg\" \\\n  --services tests/fixtures/services.valid.toml \\\n  --state-dir \"$tmpdir/state\"\n```\n\n`hello-world` is still fine for the earlier image-import smoke test, but it exits immediately, so it is not a good fit for the full apply + health check.\n\nFor a one-shot local smoke test, run:\n\n```bash\n./scripts/docker-test.sh\n```\n\nTo build just the lightweight demo package used in local docs and demos:\n\n```bash\n./scripts/make-demo-edgepkg.sh\n```\n\nTest fixtures live under `tests/fixtures/`.\n\n## More docs\n\n- [docs/cli.md](/home/marcelo/projects/caisson/docs/cli.md)\n- [docs/releases.md](/home/marcelo/projects/caisson/docs/releases.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcelo-6%2Fcaisson","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarcelo-6%2Fcaisson","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcelo-6%2Fcaisson/lists"}