{"id":49197728,"url":"https://github.com/crmne/kamal-backup","last_synced_at":"2026-04-28T17:00:57.186Z","repository":{"id":353138994,"uuid":"1218101659","full_name":"crmne/kamal-backup","owner":"crmne","description":"Scheduled backups for Rails apps deployed with Kamal","archived":false,"fork":false,"pushed_at":"2026-04-26T19:31:09.000Z","size":307,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-27T16:26:48.887Z","etag":null,"topics":["active-storage","backups","docker","kamal","mysql","postgres","restic","sqlite"],"latest_commit_sha":null,"homepage":"http://kamal-backup.dev/","language":"Ruby","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/crmne.png","metadata":{"files":{"readme":"README.md","changelog":null,"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-04-22T14:32:41.000Z","updated_at":"2026-04-27T11:17:24.000Z","dependencies_parsed_at":"2026-04-26T15:00:35.923Z","dependency_job_id":null,"html_url":"https://github.com/crmne/kamal-backup","commit_stats":null,"previous_names":["crmne/kamal-backup"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/crmne/kamal-backup","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crmne%2Fkamal-backup","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crmne%2Fkamal-backup/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crmne%2Fkamal-backup/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crmne%2Fkamal-backup/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/crmne","download_url":"https://codeload.github.com/crmne/kamal-backup/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crmne%2Fkamal-backup/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32388020,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-28T14:34:11.604Z","status":"ssl_error","status_checked_at":"2026-04-28T14:32:37.009Z","response_time":56,"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":["active-storage","backups","docker","kamal","mysql","postgres","restic","sqlite"],"created_at":"2026-04-23T12:01:08.608Z","updated_at":"2026-04-28T17:00:57.144Z","avatar_url":"https://github.com/crmne.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# kamal-backup\n\n`kamal-backup` gives Rails apps a clean backup and restore workflow for Kamal.\n\nIt backs up:\n\n- PostgreSQL, MySQL/MariaDB, or SQLite\n- file-backed Active Storage and other mounted app files\n\nIt restores in two clear modes:\n\n- `restore local`: pull a production backup onto your machine\n- `restore production`: restore back into live production\n\nAnd it drills in two clear modes:\n\n- `drill local`: prove the backup works on your machine\n- `drill production`: restore into scratch targets on production infrastructure, run checks, and record evidence\n\nUnder the hood it uses [restic](https://restic.net/) for encrypted backup storage and repository management.\n\n## Why Rails teams use it\n\n`kamal-backup` is aimed at the common self-hosted Rails setup where:\n\n- the app is deployed with Kamal\n- the database is PostgreSQL, MySQL/MariaDB, or SQLite\n- file data lives on a mounted volume\n- you need real restore drills and evidence for CASA or another security review\n\nIf your app already stores Active Storage blobs directly in S3, there may be no local file path for `BACKUP_PATHS` to capture. In that case, `kamal-backup` still covers the database side, but object-storage backups are a separate concern.\n\n## Quick Start\n\nAdd the gem in your Rails app:\n\n```ruby\ngroup :development do\n  gem \"kamal-backup\"\nend\n```\n\nInstall it and generate the shared config stub:\n\n```sh\nbundle install\nbundle exec kamal-backup init\n```\n\nThat creates:\n\n- `config/kamal-backup.yml`\n\nFor most Rails apps, that is enough. `restore local` and `drill local` can infer:\n\n- the development database target from `config/database.yml`\n- the local files target from `storage`\n- the local drill state directory from `tmp/kamal-backup`\n\nOnly create `config/kamal-backup.local.yml` if you need to override those local defaults.\n\nLocal restore and drill also require the `restic` binary on your machine. The backup accessory image already includes `restic` for production-side commands.\n\nThen add the backup accessory to `config/deploy.yml`:\n\n```yaml\naccessories:\n  backup:\n    image: ghcr.io/crmne/kamal-backup:latest\n    host: chatwithwork.com\n    env:\n      clear:\n        APP_NAME: chatwithwork\n        DATABASE_ADAPTER: postgres\n        DATABASE_URL: postgres://chatwithwork@chatwithwork-db:5432/chatwithwork_production\n        BACKUP_PATHS: /data/storage\n        RESTIC_REPOSITORY: s3:https://s3.example.com/chatwithwork-backups\n        RESTIC_INIT_IF_MISSING: \"true\"\n        BACKUP_SCHEDULE_SECONDS: \"86400\"\n      secret:\n        - PGPASSWORD\n        - RESTIC_PASSWORD\n        - AWS_ACCESS_KEY_ID\n        - AWS_SECRET_ACCESS_KEY\n    volumes:\n      - \"chatwithwork_storage:/data/storage:ro\"\n```\n\nBoot it:\n\n```sh\nbin/kamal accessory boot backup\nbin/kamal accessory logs backup\n```\n\nRun the first backup from your app checkout with the local gem and Kamal-style destination selection:\n\n```sh\nbundle exec kamal-backup -d production backup\nbundle exec kamal-backup -d production list\nbundle exec kamal-backup -d production evidence\n```\n\nIf you keep multiple deploy configs, pass `-c` the same way Kamal does:\n\n```sh\nbundle exec kamal-backup -c config/deploy.staging.yml -d staging backup\n```\n\nExamples live in:\n\n- [examples/kamal-accessory.yml](examples/kamal-accessory.yml)\n- [examples/kamal-backup.yml.example](examples/kamal-backup.yml.example)\n- [examples/kamal-backup.local.yml.example](examples/kamal-backup.local.yml.example)\n\n## What Restic Does Here\n\n`kamal-backup` uses restic as the backup engine and repository format.\n\nIn the normal Kamal setup, you do not install restic on the Rails app host. The backup accessory image already includes it. You only point the accessory at a restic repository, usually:\n\n- S3-compatible object storage\n- a restic REST server\n- a filesystem path for local development\n\nIf you choose a `rest:` repository, `kamal-backup` does not install or operate that server for you. It is a separate service.\n\n## Commands\n\nThe operator-facing command surface is:\n\n```sh\nkamal-backup init\nkamal-backup backup\nkamal-backup restore local [snapshot-or-latest]\nkamal-backup restore production [snapshot-or-latest]\nkamal-backup drill local [snapshot-or-latest]\nkamal-backup drill production [snapshot-or-latest]\nkamal-backup list\nkamal-backup check\nkamal-backup evidence\nkamal-backup schedule\nkamal-backup version\n```\n\nProduction-side commands shell out through Kamal when you pass `-d` or `-c`. Local commands run on your machine.\n\nRemote-backed commands require the local gem version and the backup accessory version to match. If they drift, `kamal-backup` fails fast and tells you to reboot the accessory so it pulls the current `latest` image. `version` is the diagnostic exception: from an app checkout with `config/deploy.yml`, it shows both versions and the sync status even without `-d`.\n\nCommon examples:\n\n```sh\nbundle exec kamal-backup -d production backup\nbundle exec kamal-backup -d production check\nbundle exec kamal-backup -d production evidence\nbundle exec kamal-backup -d production restore production latest\nbundle exec kamal-backup -d production drill production latest --database app_restore_20260423 --files /restore/files\nbundle exec kamal-backup -d production version\nbundle exec kamal-backup restore local latest\nbundle exec kamal-backup drill local latest --check \"bin/rails runner 'puts User.count'\"\n```\n\nUse `kamal-backup help`, `kamal-backup help restore`, or `kamal-backup help drill` for task-specific usage.\n\n## How a Backup Run Works\n\nWhen `kamal-backup backup` runs, it does five things:\n\n1. Validates the app name, restic repository, database settings, and `BACKUP_PATHS`.\n2. Creates a database backup with the database-native export tool.\n3. Streams that database backup into restic with tags such as `type:database`, `adapter:\u003cadapter\u003e`, and `run:\u003ctimestamp\u003e`.\n4. Runs one `restic backup` for all configured `BACKUP_PATHS`, tagged as `type:files` with the same `run:\u003ctimestamp\u003e`.\n5. Optionally runs `restic forget --prune` and `restic check`.\n\nThat shared `run:\u003ctimestamp\u003e` tag lets you match the database backup and file backup from the same run.\n\n## Restore\n\n`restore` means \"put data back.\"\n\n`restore local` runs on your machine. With `-d` or `-c`, it asks Kamal for the backup accessory config and uses that as the source of truth for:\n\n- `APP_NAME`\n- `DATABASE_ADAPTER`\n- `RESTIC_REPOSITORY`\n- `LOCAL_RESTORE_SOURCE_PATHS` from the accessory `BACKUP_PATHS`\n\nFor a normal Rails app, the local targets come from Rails conventions:\n\n- the development database in `config/database.yml`\n- `storage` as the local files target\n- `tmp/kamal-backup` as the local drill state directory\n\nYou still provide the local secrets yourself in env:\n\n- `RESTIC_PASSWORD`\n- `POSTGRES_PASSWORD` or `MYSQL_PWD` when needed\n- `RESTIC_REPOSITORY` when it is not visible through `kamal config`\n\nAnd you need `restic` installed locally and available on `PATH`.\n\nExample:\n\n```sh\nbundle exec kamal-backup -d production restore local latest\n```\n\n`restore production` is the emergency path back into the live production database and live production file paths:\n\n```sh\nbundle exec kamal-backup -d production restore production latest\n```\n\nIt prompts locally, then shells out through Kamal to the backup accessory.\n\n## Restore Drills\n\n`drill` means \"restore, check, and record the result.\"\n\n`drill local` is often the fastest proof for a small app:\n\n```sh\nbundle exec kamal-backup -d production drill local latest --check \"bin/rails runner 'puts User.count'\"\n```\n\nLike `restore local`, this runs on your machine and requires a local `restic` install.\n\n`drill production` restores into scratch targets on production infrastructure. It does not touch the live production database:\n\n```sh\nbundle exec kamal-backup -d production drill production latest \\\n  --database app_restore_20260423 \\\n  --files /restore/files \\\n  --check \"test -d /restore/files/data/storage\"\n```\n\nEvery drill writes `last_restore_drill.json` under `KAMAL_BACKUP_STATE_DIR`, and `kamal-backup evidence` includes that latest result.\n\n## Evidence for CASA and Similar Reviews\n\n`evidence` is the JSON summary you can attach to an ops record or security review.\n\nIt includes:\n\n- latest database and file snapshots\n- latest `restic check` result\n- latest restore drill result\n- retention settings\n- tool versions\n\nFor many reviews, the useful sequence is:\n\n1. scheduled backups\n2. repository checks\n3. a real restore drill\n4. `kamal-backup evidence`\n\nThat reads much better to a reviewer than \"the backup job is green.\"\n\n## Configuration Highlights\n\nCore accessory environment:\n\n```sh\nAPP_NAME=chatwithwork\nDATABASE_ADAPTER=postgres\nRESTIC_REPOSITORY=s3:https://s3.example.com/chatwithwork-backups\nRESTIC_PASSWORD=change-me\nBACKUP_PATHS=/data/storage\n```\n\nPostgreSQL:\n\n```sh\nDATABASE_ADAPTER=postgres\nDATABASE_URL=postgres://app@app-db:5432/app_production\nPGPASSWORD=change-me\n```\n\nMySQL/MariaDB:\n\n```sh\nDATABASE_ADAPTER=mysql\nDATABASE_URL=mysql2://app@app-mysql:3306/app_production\nMYSQL_PWD=change-me\n```\n\nSQLite:\n\n```sh\nDATABASE_ADAPTER=sqlite\nSQLITE_DATABASE_PATH=/data/db/production.sqlite3\n```\n\nOptional local config files:\n\n- `config/kamal-backup.yml`\n- `config/kamal-backup.local.yml`\n\n`config/kamal-backup.local.yml` is only for nonstandard local targets. Keep secrets such as `RESTIC_PASSWORD`, cloud credentials, and local DB passwords in environment variables, not in YAML files.\n\n## Docs\n\nFull docs live in [`docs/`](docs/):\n\n- [`docs/_guide/getting-started.md`](docs/_guide/getting-started.md)\n- [`docs/_guide/configuration.md`](docs/_guide/configuration.md)\n- [`docs/_guide/restore.md`](docs/_guide/restore.md)\n- [`docs/_guide/restore-drills.md`](docs/_guide/restore-drills.md)\n- [`docs/_reference/commands.md`](docs/_reference/commands.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcrmne%2Fkamal-backup","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcrmne%2Fkamal-backup","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcrmne%2Fkamal-backup/lists"}