[{"data":1,"prerenderedAt":450},["ShallowReactive",2],{"doc-\u002Fdocs\u002Fself-hosting\u002Fbackups-and-upgrades":3,"github-stars":447},{"id":4,"title":5,"body":6,"description":439,"extension":440,"meta":441,"navigation":224,"order":212,"path":442,"section":443,"seo":444,"stem":445,"__hash__":446},"docs\u002Fdocs\u002Fself-hosting\u002Fbackups-and-upgrades.md","Backups & upgrades",{"type":7,"value":8,"toc":432},"minimark",[9,13,17,41,46,49,127,130,134,137,180,183,187,194,248,255,261,265,268,286,293,297,308,347,361,376,389,403,409,428],[10,11,5],"h1",{"id":12},"backups-upgrades",[14,15,16],"p",{},"All of your business data lives in Postgres - clients, projects, time,\ninvoices, expenses, logos and receipts included. That means backups and\nupgrades are ordinary Postgres and Docker operations, with nothing\nClerq-specific to learn.",[14,18,19,20,24,25,28,29,32,33,36,37,40],{},"The examples assume the bundled ",[21,22,23],"code",{},"docker-compose.yml",", whose database service\nis named ",[21,26,27],{},"postgres"," and whose default role and database are both ",[21,30,31],{},"clerq",".\nAdjust the names if you overrode ",[21,34,35],{},"POSTGRES_USER"," or ",[21,38,39],{},"POSTGRES_DB",".",[42,43,45],"h2",{"id":44},"back-up","Back up",[14,47,48],{},"Dump the database on a schedule. A nightly cron is plenty for most setups:",[50,51,56],"pre",{"className":52,"code":53,"language":54,"meta":55,"style":55},"language-bash shiki shiki-themes github-dark","docker compose exec -T postgres \\\n  pg_dump -U clerq clerq | gzip > clerq-$(date +%F).sql.gz\n","bash","",[21,57,58,84],{"__ignoreMap":55},[59,60,63,67,71,74,78,81],"span",{"class":61,"line":62},"line",1,[59,64,66],{"class":65},"svObZ","docker",[59,68,70],{"class":69},"sU2Wk"," compose",[59,72,73],{"class":69}," exec",[59,75,77],{"class":76},"sDLfK"," -T",[59,79,80],{"class":69}," postgres",[59,82,83],{"class":76}," \\\n",[59,85,87,90,93,96,98,102,105,108,111,115,118,121,124],{"class":61,"line":86},2,[59,88,89],{"class":69},"  pg_dump",[59,91,92],{"class":76}," -U",[59,94,95],{"class":69}," clerq",[59,97,95],{"class":69},[59,99,101],{"class":100},"snl16"," |",[59,103,104],{"class":65}," gzip",[59,106,107],{"class":100}," >",[59,109,110],{"class":69}," clerq-",[59,112,114],{"class":113},"s95oV","$(",[59,116,117],{"class":65},"date",[59,119,120],{"class":69}," +%F",[59,122,123],{"class":113},")",[59,125,126],{"class":69},".sql.gz\n",[14,128,129],{},"Store the dumps off the host - object storage or another machine - so a\nsingle disk failure cannot take both the instance and its backups. Test a\nrestore into a throwaway database now and then; an untested backup is a\nguess.",[42,131,133],{"id":132},"restore","Restore",[14,135,136],{},"Restore a dump into a running, empty database:",[50,138,140],{"className":52,"code":139,"language":54,"meta":55,"style":55},"gunzip -c clerq-2026-06-14.sql.gz | \\\n  docker compose exec -T postgres psql -U clerq clerq\n",[21,141,142,157],{"__ignoreMap":55},[59,143,144,147,150,153,155],{"class":61,"line":62},[59,145,146],{"class":65},"gunzip",[59,148,149],{"class":76}," -c",[59,151,152],{"class":69}," clerq-2026-06-14.sql.gz",[59,154,101],{"class":100},[59,156,83],{"class":76},[59,158,159,162,164,166,168,170,173,175,177],{"class":61,"line":86},[59,160,161],{"class":65},"  docker",[59,163,70],{"class":69},[59,165,73],{"class":69},[59,167,77],{"class":76},[59,169,80],{"class":69},[59,171,172],{"class":69}," psql",[59,174,92],{"class":76},[59,176,95],{"class":69},[59,178,179],{"class":69}," clerq\n",[14,181,182],{},"To restore onto a fresh instance, bring the stack up once so the database and\nschema exist, then load your dump over it.",[42,184,186],{"id":185},"upgrade","Upgrade",[14,188,189,190,193],{},"Clerq is run from source, so an upgrade is a ",[21,191,192],{},"git pull"," and a rebuild:",[50,195,197],{"className":52,"code":196,"language":54,"meta":55,"style":55},"# 1. take a fresh backup first (see above)\n# 2. get the new code\ngit pull\n\n# 3. rebuild and restart\ndocker compose up -d --build\n",[21,198,199,205,210,219,226,232],{"__ignoreMap":55},[59,200,201],{"class":61,"line":62},[59,202,204],{"class":203},"sAwPA","# 1. take a fresh backup first (see above)\n",[59,206,207],{"class":61,"line":86},[59,208,209],{"class":203},"# 2. get the new code\n",[59,211,213,216],{"class":61,"line":212},3,[59,214,215],{"class":65},"git",[59,217,218],{"class":69}," pull\n",[59,220,222],{"class":61,"line":221},4,[59,223,225],{"emptyLinePlaceholder":224},true,"\n",[59,227,229],{"class":61,"line":228},5,[59,230,231],{"class":203},"# 3. rebuild and restart\n",[59,233,235,237,239,242,245],{"class":61,"line":234},6,[59,236,66],{"class":65},[59,238,70],{"class":69},[59,240,241],{"class":69}," up",[59,243,244],{"class":76}," -d",[59,246,247],{"class":76}," --build\n",[14,249,250,251,254],{},"On startup the one-shot ",[21,252,253],{},"migrate"," service runs any new migrations before the\napp container starts, so the schema is always brought up to date for you.\nMigrations are forward-only.",[256,257,258],"blockquote",{},[14,259,260],{},"Always take a fresh backup immediately before upgrading a production\ninstance, and read the release notes for any manual steps.",[42,262,264],{"id":263},"health-checks","Health checks",[14,266,267],{},"The app exposes a public health endpoint over tRPC that returns JSON without\nauthentication - useful for uptime monitors and container health checks:",[50,269,271],{"className":52,"code":270,"language":54,"meta":55,"style":55},"curl http:\u002F\u002Flocalhost:3000\u002Fapi\u002Ftrpc\u002Fhealth.ping\n# {\"result\":{\"data\":{\"json\":{\"ok\":true,\"time\":\"...\"}}}}\n",[21,272,273,281],{"__ignoreMap":55},[59,274,275,278],{"class":61,"line":62},[59,276,277],{"class":65},"curl",[59,279,280],{"class":69}," http:\u002F\u002Flocalhost:3000\u002Fapi\u002Ftrpc\u002Fhealth.ping\n",[59,282,283],{"class":61,"line":86},[59,284,285],{"class":203},"# {\"result\":{\"data\":{\"json\":{\"ok\":true,\"time\":\"...\"}}}}\n",[14,287,288,289,292],{},"A plain TCP or HTTP check against the app port works too. The database has its\nown ",[21,290,291],{},"pg_isready"," health check built into the compose file.",[42,294,296],{"id":295},"troubleshooting","Troubleshooting",[14,298,299,303,304,307],{},[300,301,302],"strong",{},"The app container exits or restarts immediately."," Almost always a missing\nor wrong ",[21,305,306],{},"DATABASE_URL",", or the database not being ready. Check the logs:",[50,309,311],{"className":52,"code":310,"language":54,"meta":55,"style":55},"docker compose logs app\ndocker compose logs migrate\ndocker compose logs postgres\n",[21,312,313,325,336],{"__ignoreMap":55},[59,314,315,317,319,322],{"class":61,"line":62},[59,316,66],{"class":65},[59,318,70],{"class":69},[59,320,321],{"class":69}," logs",[59,323,324],{"class":69}," app\n",[59,326,327,329,331,333],{"class":61,"line":86},[59,328,66],{"class":65},[59,330,70],{"class":69},[59,332,321],{"class":69},[59,334,335],{"class":69}," migrate\n",[59,337,338,340,342,344],{"class":61,"line":212},[59,339,66],{"class":65},[59,341,70],{"class":69},[59,343,321],{"class":69},[59,345,346],{"class":69}," postgres\n",[14,348,349,352,353,356,357,360],{},[300,350,351],{},"\"DATABASE_URL is not set.\""," The app could not find a connection string.\nUnder compose this is built from the ",[21,354,355],{},"POSTGRES_*"," variables - make sure your\n",[21,358,359],{},".env"," is next to the compose file and the values are set.",[14,362,363,366,367,370,371,40],{},[300,364,365],{},"\"BETTER_AUTH_SECRET is not set; cannot sign invoice PDF links.\""," Set a\n",[21,368,369],{},"BETTER_AUTH_SECRET",". See\n",[372,373,375],"a",{"href":374},"\u002Fdocs\u002Fself-hosting\u002Fenvironment-variables","Environment variables",[14,377,378,381,382,384,385,388],{},[300,379,380],{},"A low-entropy secret warning in the logs."," Your ",[21,383,369],{}," is too\nshort or too predictable. Generate a real one with ",[21,386,387],{},"openssl rand -base64 32","\nand restart. Note that changing it signs everyone out.",[14,390,391,394,395,398,399,402],{},[300,392,393],{},"Sign-in redirects fail or loop after going public."," ",[21,396,397],{},"BETTER_AUTH_URL"," does\nnot match the URL users actually reach the instance on. Set it to the exact\nexternal origin, including ",[21,400,401],{},"https:\u002F\u002F",", and restart.",[14,404,405,408],{},[300,406,407],{},"Migrations did not apply."," Re-run the one-shot step explicitly:",[50,410,412],{"className":52,"code":411,"language":54,"meta":55,"style":55},"docker compose run --rm migrate\n",[21,413,414],{"__ignoreMap":55},[59,415,416,418,420,423,426],{"class":61,"line":62},[59,417,66],{"class":65},[59,419,70],{"class":69},[59,421,422],{"class":69}," run",[59,424,425],{"class":76}," --rm",[59,427,335],{"class":69},[429,430,431],"style",{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}",{"title":55,"searchDepth":212,"depth":212,"links":433},[434,435,436,437,438],{"id":44,"depth":86,"text":45},{"id":132,"depth":86,"text":133},{"id":185,"depth":86,"text":186},{"id":263,"depth":86,"text":264},{"id":295,"depth":86,"text":296},"Keep your Clerq data safe, move between versions, and diagnose a stuck instance. All of it is standard Postgres and Docker operations.","md",{},"\u002Fdocs\u002Fself-hosting\u002Fbackups-and-upgrades","Self-hosting",{"title":5,"description":439},"docs\u002Fself-hosting\u002Fbackups-and-upgrades","ApS6bviQ5CnYr7QkiYvpagdyJ4LhBZal6_-ntjGrEhg",{"stars":448,"repo":449},0,"PunterDigital\u002Fclerq",1781535397977]