[{"data":1,"prerenderedAt":1829},["ShallowReactive",2],{"tech-simpen-bookmark-manager":3},{"id":4,"title":5,"author":6,"body":7,"category":1813,"date":1814,"description":1815,"extension":1816,"image":1817,"meta":1818,"navigation":281,"path":1820,"readingTime":1821,"seo":1822,"stem":1823,"tags":1824,"__hash__":1828},"tech\u002Ftech\u002Fsimpen-bookmark-manager.md","Self-Hosted Bookmark Manager dengan Custom Branding via Nginx","Zainul Fanani",{"type":8,"value":9,"toc":1798},"minimark",[10,14,30,35,38,72,76,81,101,111,115,118,207,212,245,249,256,831,849,852,874,877,891,895,898,1075,1087,1091,1101,1106,1165,1170,1211,1215,1221,1494,1498,1503,1518,1521,1530,1534,1542,1545,1619,1624,1635,1639,1642,1730,1737,1741,1744,1764,1771,1785,1791,1794],[11,12,13],"p",{},"Pernah pakai bookmark browser dan merasa \"ini doang?\" — nggak bisa diakses dari device lain, nggak ada tag, nggak bisa search. Atau pakai layanan bookmark online tapi khawatir privacy? Self-hosted bookmark manager jawabannya.",[11,15,16,17,21,22,29],{},"Di tutorial ini, aku bahas setup ",[18,19,20],"strong",{},"Karakeep"," — open-source bookmark manager yang feature-complete — dan trik ",[18,23,24,25],{},"custom branding via Nginx ",[26,27,28],"code",{},"sub_filter"," tanpa edit satu baris pun kode source-nya.",[31,32,34],"h2",{"id":33},"kenapa-self-host-bookmark-manager","🤔 Kenapa Self-Host Bookmark Manager?",[11,36,37],{},"Beberapa alasan kenapa self-host lebih masuk akal buat personal atau team use:",[39,40,41,48,54,60,66],"ul",{},[42,43,44,47],"li",{},[18,45,46],{},"Privacy"," — data kamu nggak dijual atau dianalisis pihak ketiga",[42,49,50,53],{},[18,51,52],{},"Control"," — kamu yang tentukan fitur, UI, dan branding",[42,55,56,59],{},[18,57,58],{},"No vendor lock-in"," — data ada di server sendiri, export kapan aja",[42,61,62,65],{},[18,63,64],{},"Full-text search"," — dengan Meilisearch, cari bookmark by content, bukan cuma judul",[42,67,68,71],{},[18,69,70],{},"AI-powered tagging"," — Karakeep bisa auto-tag pakai AI",[31,73,75],{"id":74},"apa-itu-karakeep","📌 Apa itu Karakeep?",[11,77,78,80],{},[18,79,20],{}," (sebelumnya Hoarder) adalah open-source bookmark manager yang support:",[39,82,83,86,89,92,95,98],{},[42,84,85],{},"Bookmark URL, text notes, dan media",[42,87,88],{},"Auto-tagging pakai AI (OpenAI, Ollama, dll)",[42,90,91],{},"Full-text search via Meilisearch",[42,93,94],{},"Browser extension (Chrome\u002FFirefox)",[42,96,97],{},"Clean UI dengan dark mode",[42,99,100],{},"REST API",[11,102,103,104],{},"Repo: ",[105,106,110],"a",{"href":107,"rel":108},"https:\u002F\u002Fgithub.com\u002Fkarakeep-app\u002Fkarakeep",[109],"nofollow","github.com\u002Fkarakeep-app\u002Fkarakeep",[31,112,114],{"id":113},"️-architecture","🏗️ Architecture",[11,116,117],{},"Diagram berikut menunjukkan bagaimana stack ini bekerja:",[119,120,125],"pre",{"className":121,"code":122,"language":123,"meta":124,"style":124},"language-mermaid shiki shiki-themes github-light github-dark","flowchart LR\n    A[🖥️ Browser] --> B[🔒 Nginx Reverse Proxy]\n    B -->|sub_filter + CSS Injection| C[🐳 Karakeep App]\n    B --> D[🔍 Meilisearch]\n    B --> E[🌐 Headless Chrome]\n    C --> F[🐘 PostgreSQL]\n    \n    style A fill:#4A90D9,color:#fff\n    style B fill:#F5A623,color:#fff\n    style C fill:#7ED321,color:#fff\n    style D fill:#9B59B6,color:#fff\n    style E fill:#E74C3C,color:#fff\n    style F fill:#3498DB,color:#fff\n","mermaid","",[26,126,127,135,141,147,153,159,165,171,177,183,189,195,201],{"__ignoreMap":124},[128,129,132],"span",{"class":130,"line":131},"line",1,[128,133,134],{},"flowchart LR\n",[128,136,138],{"class":130,"line":137},2,[128,139,140],{},"    A[🖥️ Browser] --> B[🔒 Nginx Reverse Proxy]\n",[128,142,144],{"class":130,"line":143},3,[128,145,146],{},"    B -->|sub_filter + CSS Injection| C[🐳 Karakeep App]\n",[128,148,150],{"class":130,"line":149},4,[128,151,152],{},"    B --> D[🔍 Meilisearch]\n",[128,154,156],{"class":130,"line":155},5,[128,157,158],{},"    B --> E[🌐 Headless Chrome]\n",[128,160,162],{"class":130,"line":161},6,[128,163,164],{},"    C --> F[🐘 PostgreSQL]\n",[128,166,168],{"class":130,"line":167},7,[128,169,170],{},"    \n",[128,172,174],{"class":130,"line":173},8,[128,175,176],{},"    style A fill:#4A90D9,color:#fff\n",[128,178,180],{"class":130,"line":179},9,[128,181,182],{},"    style B fill:#F5A623,color:#fff\n",[128,184,186],{"class":130,"line":185},10,[128,187,188],{},"    style C fill:#7ED321,color:#fff\n",[128,190,192],{"class":130,"line":191},11,[128,193,194],{},"    style D fill:#9B59B6,color:#fff\n",[128,196,198],{"class":130,"line":197},12,[128,199,200],{},"    style E fill:#E74C3C,color:#fff\n",[128,202,204],{"class":130,"line":203},13,[128,205,206],{},"    style F fill:#3498DB,color:#fff\n",[11,208,209],{},[18,210,211],{},"Komponen:",[39,213,214,222,227,233,239],{},[42,215,216,219,220],{},[18,217,218],{},"Nginx"," — reverse proxy + SSL + custom branding via ",[26,221,28],{},[42,223,224,226],{},[18,225,20],{}," — main app (Next.js)",[42,228,229,232],{},[18,230,231],{},"Meilisearch"," — full-text search engine",[42,234,235,238],{},[18,236,237],{},"Chrome\u002FChromium"," — headless browser untuk render bookmark preview",[42,240,241,244],{},[18,242,243],{},"PostgreSQL"," — database utama",[31,246,248],{"id":247},"docker-compose-setup","🚀 Docker Compose Setup",[11,250,251,252,255],{},"Buat folder project dan ",[26,253,254],{},"docker-compose.yml",":",[119,257,261],{"className":258,"code":259,"language":260,"meta":124,"style":124},"language-yaml shiki shiki-themes github-light github-dark","version: \"3.8\"\n\nservices:\n  app:\n    image: ghcr.io\u002Fkarakeep-app\u002Fkarakeep:latest\n    restart: unless-stopped\n    ports:\n      - \"3000:3000\"\n    environment:\n      - NEXT_PUBLIC_URL=https:\u002F\u002Fbookmarks.example.com\n      - NEXT_PUBLIC_DISABLE_SIGNUP=false\n      - MEILI_ADDR=http:\u002F\u002Fmeilisearch:7700\n      - DATA_DIR=\u002Fdata\n      - NEXTAUTH_SECRET=changeme-to-random-string\n      - NEXTAUTH_URL=https:\u002F\u002Fbookmarks.example.com\n    volumes:\n      - app-data:\u002Fdata\n    depends_on:\n      meilisearch:\n        condition: service_healthy\n      chrome:\n        condition: service_started\n      db:\n        condition: service_healthy\n\n  meilisearch:\n    image: getmeili\u002Fmeilisearch:v1.6\n    restart: unless-stopped\n    volumes:\n      - meili-data:\u002Fmeili_data\n    environment:\n      - MEILI_ENV=production\n      - MEILI_MASTER_KEY=changeme-master-key\n    healthcheck:\n      test: [\"CMD\", \"wget\", \"--spider\", \"-q\", \"http:\u002F\u002Flocalhost:7700\u002Fhealth\"]\n      interval: 10s\n      timeout: 5s\n      retries: 5\n\n  chrome:\n    image: ghcr.io\u002Fbrowserless\u002Fchromium:v2\n    restart: unless-stopped\n    environment:\n      - TIMEOUT=30000\n      - MAX_CONCURRENT_SESSIONS=4\n\n  db:\n    image: postgres:16-alpine\n    restart: unless-stopped\n    environment:\n      - POSTGRES_USER=karakeep\n      - POSTGRES_PASSWORD=changeme-db-password\n      - POSTGRES_DB=karakeep\n    volumes:\n      - db-data:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata\n    healthcheck:\n      test: [\"CMD-SHELL\", \"pg_isready -U karakeep\"]\n      interval: 10s\n      timeout: 5s\n      retries: 5\n\nvolumes:\n  app-data:\n  meili-data:\n  db-data:\n","yaml",[26,262,263,277,283,291,298,308,318,325,333,340,347,354,361,368,376,384,392,400,408,416,427,435,445,453,462,467,475,485,494,501,509,516,524,532,540,576,587,598,610,615,623,633,642,649,657,665,670,678,688,697,704,712,720,728,735,743,750,767,776,785,794,799,807,815,823],{"__ignoreMap":124},[128,264,265,269,273],{"class":130,"line":131},[128,266,268],{"class":267},"s9eBZ","version",[128,270,272],{"class":271},"sVt8B",": ",[128,274,276],{"class":275},"sZZnC","\"3.8\"\n",[128,278,279],{"class":130,"line":137},[128,280,282],{"emptyLinePlaceholder":281},true,"\n",[128,284,285,288],{"class":130,"line":143},[128,286,287],{"class":267},"services",[128,289,290],{"class":271},":\n",[128,292,293,296],{"class":130,"line":149},[128,294,295],{"class":267},"  app",[128,297,290],{"class":271},[128,299,300,303,305],{"class":130,"line":155},[128,301,302],{"class":267},"    image",[128,304,272],{"class":271},[128,306,307],{"class":275},"ghcr.io\u002Fkarakeep-app\u002Fkarakeep:latest\n",[128,309,310,313,315],{"class":130,"line":161},[128,311,312],{"class":267},"    restart",[128,314,272],{"class":271},[128,316,317],{"class":275},"unless-stopped\n",[128,319,320,323],{"class":130,"line":167},[128,321,322],{"class":267},"    ports",[128,324,290],{"class":271},[128,326,327,330],{"class":130,"line":173},[128,328,329],{"class":271},"      - ",[128,331,332],{"class":275},"\"3000:3000\"\n",[128,334,335,338],{"class":130,"line":179},[128,336,337],{"class":267},"    environment",[128,339,290],{"class":271},[128,341,342,344],{"class":130,"line":185},[128,343,329],{"class":271},[128,345,346],{"class":275},"NEXT_PUBLIC_URL=https:\u002F\u002Fbookmarks.example.com\n",[128,348,349,351],{"class":130,"line":191},[128,350,329],{"class":271},[128,352,353],{"class":275},"NEXT_PUBLIC_DISABLE_SIGNUP=false\n",[128,355,356,358],{"class":130,"line":197},[128,357,329],{"class":271},[128,359,360],{"class":275},"MEILI_ADDR=http:\u002F\u002Fmeilisearch:7700\n",[128,362,363,365],{"class":130,"line":203},[128,364,329],{"class":271},[128,366,367],{"class":275},"DATA_DIR=\u002Fdata\n",[128,369,371,373],{"class":130,"line":370},14,[128,372,329],{"class":271},[128,374,375],{"class":275},"NEXTAUTH_SECRET=changeme-to-random-string\n",[128,377,379,381],{"class":130,"line":378},15,[128,380,329],{"class":271},[128,382,383],{"class":275},"NEXTAUTH_URL=https:\u002F\u002Fbookmarks.example.com\n",[128,385,387,390],{"class":130,"line":386},16,[128,388,389],{"class":267},"    volumes",[128,391,290],{"class":271},[128,393,395,397],{"class":130,"line":394},17,[128,396,329],{"class":271},[128,398,399],{"class":275},"app-data:\u002Fdata\n",[128,401,403,406],{"class":130,"line":402},18,[128,404,405],{"class":267},"    depends_on",[128,407,290],{"class":271},[128,409,411,414],{"class":130,"line":410},19,[128,412,413],{"class":267},"      meilisearch",[128,415,290],{"class":271},[128,417,419,422,424],{"class":130,"line":418},20,[128,420,421],{"class":267},"        condition",[128,423,272],{"class":271},[128,425,426],{"class":275},"service_healthy\n",[128,428,430,433],{"class":130,"line":429},21,[128,431,432],{"class":267},"      chrome",[128,434,290],{"class":271},[128,436,438,440,442],{"class":130,"line":437},22,[128,439,421],{"class":267},[128,441,272],{"class":271},[128,443,444],{"class":275},"service_started\n",[128,446,448,451],{"class":130,"line":447},23,[128,449,450],{"class":267},"      db",[128,452,290],{"class":271},[128,454,456,458,460],{"class":130,"line":455},24,[128,457,421],{"class":267},[128,459,272],{"class":271},[128,461,426],{"class":275},[128,463,465],{"class":130,"line":464},25,[128,466,282],{"emptyLinePlaceholder":281},[128,468,470,473],{"class":130,"line":469},26,[128,471,472],{"class":267},"  meilisearch",[128,474,290],{"class":271},[128,476,478,480,482],{"class":130,"line":477},27,[128,479,302],{"class":267},[128,481,272],{"class":271},[128,483,484],{"class":275},"getmeili\u002Fmeilisearch:v1.6\n",[128,486,488,490,492],{"class":130,"line":487},28,[128,489,312],{"class":267},[128,491,272],{"class":271},[128,493,317],{"class":275},[128,495,497,499],{"class":130,"line":496},29,[128,498,389],{"class":267},[128,500,290],{"class":271},[128,502,504,506],{"class":130,"line":503},30,[128,505,329],{"class":271},[128,507,508],{"class":275},"meili-data:\u002Fmeili_data\n",[128,510,512,514],{"class":130,"line":511},31,[128,513,337],{"class":267},[128,515,290],{"class":271},[128,517,519,521],{"class":130,"line":518},32,[128,520,329],{"class":271},[128,522,523],{"class":275},"MEILI_ENV=production\n",[128,525,527,529],{"class":130,"line":526},33,[128,528,329],{"class":271},[128,530,531],{"class":275},"MEILI_MASTER_KEY=changeme-master-key\n",[128,533,535,538],{"class":130,"line":534},34,[128,536,537],{"class":267},"    healthcheck",[128,539,290],{"class":271},[128,541,543,546,549,552,555,558,560,563,565,568,570,573],{"class":130,"line":542},35,[128,544,545],{"class":267},"      test",[128,547,548],{"class":271},": [",[128,550,551],{"class":275},"\"CMD\"",[128,553,554],{"class":271},", ",[128,556,557],{"class":275},"\"wget\"",[128,559,554],{"class":271},[128,561,562],{"class":275},"\"--spider\"",[128,564,554],{"class":271},[128,566,567],{"class":275},"\"-q\"",[128,569,554],{"class":271},[128,571,572],{"class":275},"\"http:\u002F\u002Flocalhost:7700\u002Fhealth\"",[128,574,575],{"class":271},"]\n",[128,577,579,582,584],{"class":130,"line":578},36,[128,580,581],{"class":267},"      interval",[128,583,272],{"class":271},[128,585,586],{"class":275},"10s\n",[128,588,590,593,595],{"class":130,"line":589},37,[128,591,592],{"class":267},"      timeout",[128,594,272],{"class":271},[128,596,597],{"class":275},"5s\n",[128,599,601,604,606],{"class":130,"line":600},38,[128,602,603],{"class":267},"      retries",[128,605,272],{"class":271},[128,607,609],{"class":608},"sj4cs","5\n",[128,611,613],{"class":130,"line":612},39,[128,614,282],{"emptyLinePlaceholder":281},[128,616,618,621],{"class":130,"line":617},40,[128,619,620],{"class":267},"  chrome",[128,622,290],{"class":271},[128,624,626,628,630],{"class":130,"line":625},41,[128,627,302],{"class":267},[128,629,272],{"class":271},[128,631,632],{"class":275},"ghcr.io\u002Fbrowserless\u002Fchromium:v2\n",[128,634,636,638,640],{"class":130,"line":635},42,[128,637,312],{"class":267},[128,639,272],{"class":271},[128,641,317],{"class":275},[128,643,645,647],{"class":130,"line":644},43,[128,646,337],{"class":267},[128,648,290],{"class":271},[128,650,652,654],{"class":130,"line":651},44,[128,653,329],{"class":271},[128,655,656],{"class":275},"TIMEOUT=30000\n",[128,658,660,662],{"class":130,"line":659},45,[128,661,329],{"class":271},[128,663,664],{"class":275},"MAX_CONCURRENT_SESSIONS=4\n",[128,666,668],{"class":130,"line":667},46,[128,669,282],{"emptyLinePlaceholder":281},[128,671,673,676],{"class":130,"line":672},47,[128,674,675],{"class":267},"  db",[128,677,290],{"class":271},[128,679,681,683,685],{"class":130,"line":680},48,[128,682,302],{"class":267},[128,684,272],{"class":271},[128,686,687],{"class":275},"postgres:16-alpine\n",[128,689,691,693,695],{"class":130,"line":690},49,[128,692,312],{"class":267},[128,694,272],{"class":271},[128,696,317],{"class":275},[128,698,700,702],{"class":130,"line":699},50,[128,701,337],{"class":267},[128,703,290],{"class":271},[128,705,707,709],{"class":130,"line":706},51,[128,708,329],{"class":271},[128,710,711],{"class":275},"POSTGRES_USER=karakeep\n",[128,713,715,717],{"class":130,"line":714},52,[128,716,329],{"class":271},[128,718,719],{"class":275},"POSTGRES_PASSWORD=changeme-db-password\n",[128,721,723,725],{"class":130,"line":722},53,[128,724,329],{"class":271},[128,726,727],{"class":275},"POSTGRES_DB=karakeep\n",[128,729,731,733],{"class":130,"line":730},54,[128,732,389],{"class":267},[128,734,290],{"class":271},[128,736,738,740],{"class":130,"line":737},55,[128,739,329],{"class":271},[128,741,742],{"class":275},"db-data:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata\n",[128,744,746,748],{"class":130,"line":745},56,[128,747,537],{"class":267},[128,749,290],{"class":271},[128,751,753,755,757,760,762,765],{"class":130,"line":752},57,[128,754,545],{"class":267},[128,756,548],{"class":271},[128,758,759],{"class":275},"\"CMD-SHELL\"",[128,761,554],{"class":271},[128,763,764],{"class":275},"\"pg_isready -U karakeep\"",[128,766,575],{"class":271},[128,768,770,772,774],{"class":130,"line":769},58,[128,771,581],{"class":267},[128,773,272],{"class":271},[128,775,586],{"class":275},[128,777,779,781,783],{"class":130,"line":778},59,[128,780,592],{"class":267},[128,782,272],{"class":271},[128,784,597],{"class":275},[128,786,788,790,792],{"class":130,"line":787},60,[128,789,603],{"class":267},[128,791,272],{"class":271},[128,793,609],{"class":608},[128,795,797],{"class":130,"line":796},61,[128,798,282],{"emptyLinePlaceholder":281},[128,800,802,805],{"class":130,"line":801},62,[128,803,804],{"class":267},"volumes",[128,806,290],{"class":271},[128,808,810,813],{"class":130,"line":809},63,[128,811,812],{"class":267},"  app-data",[128,814,290],{"class":271},[128,816,818,821],{"class":130,"line":817},64,[128,819,820],{"class":267},"  meili-data",[128,822,290],{"class":271},[128,824,826,829],{"class":130,"line":825},65,[128,827,828],{"class":267},"  db-data",[128,830,290],{"class":271},[832,833,834],"blockquote",{},[11,835,836,837,840,841,844,845,848],{},"⚠️ ",[18,838,839],{},"Penting:"," Ganti semua ",[26,842,843],{},"changeme-*"," value dengan string random yang kuat. Bisa generate pakai ",[26,846,847],{},"openssl rand -hex 32",".",[11,850,851],{},"Jalankan:",[119,853,857],{"className":854,"code":855,"language":856,"meta":124,"style":124},"language-bash shiki shiki-themes github-light github-dark","docker compose up -d\n","bash",[26,858,859],{"__ignoreMap":124},[128,860,861,865,868,871],{"class":130,"line":131},[128,862,864],{"class":863},"sScJk","docker",[128,866,867],{"class":275}," compose",[128,869,870],{"class":275}," up",[128,872,873],{"class":608}," -d\n",[11,875,876],{},"Cek semua container running:",[119,878,880],{"className":854,"code":879,"language":856,"meta":124,"style":124},"docker compose ps\n",[26,881,882],{"__ignoreMap":124},[128,883,884,886,888],{"class":130,"line":131},[128,885,864],{"class":863},[128,887,867],{"class":275},[128,889,890],{"class":275}," ps\n",[31,892,894],{"id":893},"nginx-reverse-proxy","🔧 Nginx Reverse Proxy",[11,896,897],{},"Selanjutnya setup Nginx sebagai reverse proxy dengan SSL. Ini juga tempat kita taruh magic custom branding.",[119,899,903],{"className":900,"code":901,"language":902,"meta":124,"style":124},"language-nginx shiki shiki-themes github-light github-dark","server {\n    listen 80;\n    server_name bookmarks.example.com;\n    return 301 https:\u002F\u002F$host$request_uri;\n}\n\nserver {\n    listen 443 ssl http2;\n    server_name bookmarks.example.com;\n\n    ssl_certificate     \u002Fetc\u002Fletsencrypt\u002Flive\u002Fbookmarks.example.com\u002Ffullchain.pem;\n    ssl_certificate_key \u002Fetc\u002Fletsencrypt\u002Flive\u002Fbookmarks.example.com\u002Fprivkey.pem;\n\n    client_max_body_size 50M;\n\n    location \u002F {\n        proxy_pass http:\u002F\u002F127.0.0.1:3000;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n\n        # --- CUSTOM BRANDING ---\n        proxy_set_header Accept-Encoding \"\";\n        sub_filter '\u003C\u002Fhead>' '\u003Clink rel=\"stylesheet\" href=\"\u002Fcustom-branding.css\">\u003Cstyle>.custom-logo{display:none !important}\u003C\u002Fstyle>\u003C\u002Fhead>';\n        sub_filter '\u003Ctitle>Karakeep' '\u003Ctitle>MyMarks';\n        sub_filter 'Karakeep' 'MyMarks';\n        sub_filter_once off;\n        sub_filter_types text\u002Fhtml text\u002Fcss application\u002Fjavascript application\u002Fjson;\n    }\n\n    location \u002Fcustom-branding.css {\n        alias \u002Fvar\u002Fwww\u002Fbookmarks\u002Fcustom-branding.css;\n        expires 1d;\n    }\n}\n","nginx",[26,904,905,910,915,920,925,930,934,938,943,947,951,956,961,965,970,974,979,984,989,994,999,1004,1008,1013,1018,1023,1028,1033,1038,1043,1048,1052,1057,1062,1067,1071],{"__ignoreMap":124},[128,906,907],{"class":130,"line":131},[128,908,909],{},"server {\n",[128,911,912],{"class":130,"line":137},[128,913,914],{},"    listen 80;\n",[128,916,917],{"class":130,"line":143},[128,918,919],{},"    server_name bookmarks.example.com;\n",[128,921,922],{"class":130,"line":149},[128,923,924],{},"    return 301 https:\u002F\u002F$host$request_uri;\n",[128,926,927],{"class":130,"line":155},[128,928,929],{},"}\n",[128,931,932],{"class":130,"line":161},[128,933,282],{"emptyLinePlaceholder":281},[128,935,936],{"class":130,"line":167},[128,937,909],{},[128,939,940],{"class":130,"line":173},[128,941,942],{},"    listen 443 ssl http2;\n",[128,944,945],{"class":130,"line":179},[128,946,919],{},[128,948,949],{"class":130,"line":185},[128,950,282],{"emptyLinePlaceholder":281},[128,952,953],{"class":130,"line":191},[128,954,955],{},"    ssl_certificate     \u002Fetc\u002Fletsencrypt\u002Flive\u002Fbookmarks.example.com\u002Ffullchain.pem;\n",[128,957,958],{"class":130,"line":197},[128,959,960],{},"    ssl_certificate_key \u002Fetc\u002Fletsencrypt\u002Flive\u002Fbookmarks.example.com\u002Fprivkey.pem;\n",[128,962,963],{"class":130,"line":203},[128,964,282],{"emptyLinePlaceholder":281},[128,966,967],{"class":130,"line":370},[128,968,969],{},"    client_max_body_size 50M;\n",[128,971,972],{"class":130,"line":378},[128,973,282],{"emptyLinePlaceholder":281},[128,975,976],{"class":130,"line":386},[128,977,978],{},"    location \u002F {\n",[128,980,981],{"class":130,"line":394},[128,982,983],{},"        proxy_pass http:\u002F\u002F127.0.0.1:3000;\n",[128,985,986],{"class":130,"line":402},[128,987,988],{},"        proxy_set_header Host $host;\n",[128,990,991],{"class":130,"line":410},[128,992,993],{},"        proxy_set_header X-Real-IP $remote_addr;\n",[128,995,996],{"class":130,"line":418},[128,997,998],{},"        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n",[128,1000,1001],{"class":130,"line":429},[128,1002,1003],{},"        proxy_set_header X-Forwarded-Proto $scheme;\n",[128,1005,1006],{"class":130,"line":437},[128,1007,282],{"emptyLinePlaceholder":281},[128,1009,1010],{"class":130,"line":447},[128,1011,1012],{},"        # --- CUSTOM BRANDING ---\n",[128,1014,1015],{"class":130,"line":455},[128,1016,1017],{},"        proxy_set_header Accept-Encoding \"\";\n",[128,1019,1020],{"class":130,"line":464},[128,1021,1022],{},"        sub_filter '\u003C\u002Fhead>' '\u003Clink rel=\"stylesheet\" href=\"\u002Fcustom-branding.css\">\u003Cstyle>.custom-logo{display:none !important}\u003C\u002Fstyle>\u003C\u002Fhead>';\n",[128,1024,1025],{"class":130,"line":469},[128,1026,1027],{},"        sub_filter '\u003Ctitle>Karakeep' '\u003Ctitle>MyMarks';\n",[128,1029,1030],{"class":130,"line":477},[128,1031,1032],{},"        sub_filter 'Karakeep' 'MyMarks';\n",[128,1034,1035],{"class":130,"line":487},[128,1036,1037],{},"        sub_filter_once off;\n",[128,1039,1040],{"class":130,"line":496},[128,1041,1042],{},"        sub_filter_types text\u002Fhtml text\u002Fcss application\u002Fjavascript application\u002Fjson;\n",[128,1044,1045],{"class":130,"line":503},[128,1046,1047],{},"    }\n",[128,1049,1050],{"class":130,"line":511},[128,1051,282],{"emptyLinePlaceholder":281},[128,1053,1054],{"class":130,"line":518},[128,1055,1056],{},"    location \u002Fcustom-branding.css {\n",[128,1058,1059],{"class":130,"line":526},[128,1060,1061],{},"        alias \u002Fvar\u002Fwww\u002Fbookmarks\u002Fcustom-branding.css;\n",[128,1063,1064],{"class":130,"line":534},[128,1065,1066],{},"        expires 1d;\n",[128,1068,1069],{"class":130,"line":542},[128,1070,1047],{},[128,1072,1073],{"class":130,"line":578},[128,1074,929],{},[832,1076,1077],{},[11,1078,1079,1080,1083,1084],{},"💡 ",[18,1081,1082],{},"Tips:"," Untuk SSL, bisa pakai Certbot: ",[26,1085,1086],{},"certbot --nginx -d bookmarks.example.com",[31,1088,1090],{"id":1089},"custom-branding-via-sub_filter","🎨 Custom Branding via sub_filter",[11,1092,1093,1094,1096,1097,1100],{},"Ini adalah bagian paling menarik dari tutorial ini. Dengan Nginx ",[26,1095,28],{},", kita bisa mengubah branding aplikasi ",[18,1098,1099],{},"tanpa menyentuh source code"," sama sekali.",[1102,1103,1105],"h3",{"id":1104},"bagaimana-sub_filter-bekerja","Bagaimana sub_filter Bekerja?",[119,1107,1109],{"className":121,"code":1108,"language":123,"meta":124,"style":124},"flowchart TD\n    A[\"Original HTML dari Karakeep\"] --> B[\"Nginx intercept response\"]\n    B --> C[\"sub_filter processing\"]\n    C --> D[\"CSS Injection via sub_filter\"]\n    D --> E[\"Modified HTML ke Browser\"]\n\n    style A fill:#E8F5E9\n    style B fill:#FFF3E0\n    style C fill:#F3E5F5\n    style D fill:#E3F2FD\n    style E fill:#E8F5E9\n",[26,1110,1111,1116,1121,1126,1131,1136,1140,1145,1150,1155,1160],{"__ignoreMap":124},[128,1112,1113],{"class":130,"line":131},[128,1114,1115],{},"flowchart TD\n",[128,1117,1118],{"class":130,"line":137},[128,1119,1120],{},"    A[\"Original HTML dari Karakeep\"] --> B[\"Nginx intercept response\"]\n",[128,1122,1123],{"class":130,"line":143},[128,1124,1125],{},"    B --> C[\"sub_filter processing\"]\n",[128,1127,1128],{"class":130,"line":149},[128,1129,1130],{},"    C --> D[\"CSS Injection via sub_filter\"]\n",[128,1132,1133],{"class":130,"line":155},[128,1134,1135],{},"    D --> E[\"Modified HTML ke Browser\"]\n",[128,1137,1138],{"class":130,"line":161},[128,1139,282],{"emptyLinePlaceholder":281},[128,1141,1142],{"class":130,"line":167},[128,1143,1144],{},"    style A fill:#E8F5E9\n",[128,1146,1147],{"class":130,"line":173},[128,1148,1149],{},"    style B fill:#FFF3E0\n",[128,1151,1152],{"class":130,"line":179},[128,1153,1154],{},"    style C fill:#F3E5F5\n",[128,1156,1157],{"class":130,"line":185},[128,1158,1159],{},"    style D fill:#E3F2FD\n",[128,1161,1162],{"class":130,"line":191},[128,1163,1164],{},"    style E fill:#E8F5E9\n",[11,1166,1167],{},[18,1168,1169],{},"Key steps:",[1171,1172,1173,1183,1192,1202],"ol",{},[42,1174,1175,1178,1179,1182],{},[18,1176,1177],{},"Disable compression"," — ",[26,1180,1181],{},"proxy_set_header Accept-Encoding \"\";"," supaya Nginx bisa baca dan modify response body",[42,1184,1185,1178,1188,1191],{},[18,1186,1187],{},"Text replacement",[26,1189,1190],{},"sub_filter 'Karakeep' 'MyMarks'"," mengganti semua occurrence",[42,1193,1194,1197,1198,1201],{},[18,1195,1196],{},"CSS injection"," — inject custom stylesheet ke ",[26,1199,1200],{},"\u003Chead>"," untuk override styling",[42,1203,1204,1178,1207,1210],{},[18,1205,1206],{},"Recursive replacement",[26,1208,1209],{},"sub_filter_once off"," memastikan semua occurrence diganti",[1102,1212,1214],{"id":1213},"file-custom-brandingcss","File custom-branding.css",[11,1216,1217,1218,255],{},"Buat file ",[26,1219,1220],{},"\u002Fvar\u002Fwww\u002Fbookmarks\u002Fcustom-branding.css",[119,1222,1226],{"className":1223,"code":1224,"language":1225,"meta":124,"style":124},"language-css shiki shiki-themes github-light github-dark","\u002F* === MyMarks Custom Branding === *\u002F\n\n\u002F* Override logo *\u002F\n.logo-container img {\n    content: url(\"https:\u002F\u002Fbookmarks.example.com\u002Flogo.svg\");\n    height: 32px;\n}\n\n\u002F* Override app name in header *\u002F\nheader .app-name {\n    font-family: 'Inter', sans-serif;\n    font-weight: 700;\n    letter-spacing: -0.5px;\n}\n\n\u002F* Custom brand colors *\u002F\n:root {\n    --brand-primary: #6366f1;\n    --brand-secondary: #8b5cf6;\n}\n\n\u002F* Override primary buttons *\u002F\nbutton.primary,\na.primary-btn {\n    background-color: var(--brand-primary) !important;\n    border-color: var(--brand-primary) !important;\n}\n\n\u002F* Favicon (limited - needs separate approach) *\u002F\n\u002F* See tips section below for favicon handling *\u002F\n","css",[26,1227,1228,1234,1238,1243,1254,1273,1290,1294,1298,1303,1313,1330,1342,1356,1360,1364,1369,1376,1389,1401,1405,1409,1414,1425,1434,1457,1476,1480,1484,1489],{"__ignoreMap":124},[128,1229,1230],{"class":130,"line":131},[128,1231,1233],{"class":1232},"sJ8bj","\u002F* === MyMarks Custom Branding === *\u002F\n",[128,1235,1236],{"class":130,"line":137},[128,1237,282],{"emptyLinePlaceholder":281},[128,1239,1240],{"class":130,"line":143},[128,1241,1242],{"class":1232},"\u002F* Override logo *\u002F\n",[128,1244,1245,1248,1251],{"class":130,"line":149},[128,1246,1247],{"class":863},".logo-container",[128,1249,1250],{"class":267}," img",[128,1252,1253],{"class":271}," {\n",[128,1255,1256,1259,1261,1264,1267,1270],{"class":130,"line":155},[128,1257,1258],{"class":608},"    content",[128,1260,272],{"class":271},[128,1262,1263],{"class":608},"url",[128,1265,1266],{"class":271},"(",[128,1268,1269],{"class":275},"\"https:\u002F\u002Fbookmarks.example.com\u002Flogo.svg\"",[128,1271,1272],{"class":271},");\n",[128,1274,1275,1278,1280,1283,1287],{"class":130,"line":161},[128,1276,1277],{"class":608},"    height",[128,1279,272],{"class":271},[128,1281,1282],{"class":608},"32",[128,1284,1286],{"class":1285},"szBVR","px",[128,1288,1289],{"class":271},";\n",[128,1291,1292],{"class":130,"line":167},[128,1293,929],{"class":271},[128,1295,1296],{"class":130,"line":173},[128,1297,282],{"emptyLinePlaceholder":281},[128,1299,1300],{"class":130,"line":179},[128,1301,1302],{"class":1232},"\u002F* Override app name in header *\u002F\n",[128,1304,1305,1308,1311],{"class":130,"line":185},[128,1306,1307],{"class":267},"header",[128,1309,1310],{"class":863}," .app-name",[128,1312,1253],{"class":271},[128,1314,1315,1318,1320,1323,1325,1328],{"class":130,"line":191},[128,1316,1317],{"class":608},"    font-family",[128,1319,272],{"class":271},[128,1321,1322],{"class":275},"'Inter'",[128,1324,554],{"class":271},[128,1326,1327],{"class":608},"sans-serif",[128,1329,1289],{"class":271},[128,1331,1332,1335,1337,1340],{"class":130,"line":197},[128,1333,1334],{"class":608},"    font-weight",[128,1336,272],{"class":271},[128,1338,1339],{"class":608},"700",[128,1341,1289],{"class":271},[128,1343,1344,1347,1349,1352,1354],{"class":130,"line":203},[128,1345,1346],{"class":608},"    letter-spacing",[128,1348,272],{"class":271},[128,1350,1351],{"class":608},"-0.5",[128,1353,1286],{"class":1285},[128,1355,1289],{"class":271},[128,1357,1358],{"class":130,"line":370},[128,1359,929],{"class":271},[128,1361,1362],{"class":130,"line":378},[128,1363,282],{"emptyLinePlaceholder":281},[128,1365,1366],{"class":130,"line":386},[128,1367,1368],{"class":1232},"\u002F* Custom brand colors *\u002F\n",[128,1370,1371,1374],{"class":130,"line":394},[128,1372,1373],{"class":863},":root",[128,1375,1253],{"class":271},[128,1377,1378,1382,1384,1387],{"class":130,"line":402},[128,1379,1381],{"class":1380},"s4XuR","    --brand-primary",[128,1383,272],{"class":271},[128,1385,1386],{"class":608},"#6366f1",[128,1388,1289],{"class":271},[128,1390,1391,1394,1396,1399],{"class":130,"line":410},[128,1392,1393],{"class":1380},"    --brand-secondary",[128,1395,272],{"class":271},[128,1397,1398],{"class":608},"#8b5cf6",[128,1400,1289],{"class":271},[128,1402,1403],{"class":130,"line":418},[128,1404,929],{"class":271},[128,1406,1407],{"class":130,"line":429},[128,1408,282],{"emptyLinePlaceholder":281},[128,1410,1411],{"class":130,"line":437},[128,1412,1413],{"class":1232},"\u002F* Override primary buttons *\u002F\n",[128,1415,1416,1419,1422],{"class":130,"line":447},[128,1417,1418],{"class":267},"button",[128,1420,1421],{"class":863},".primary",[128,1423,1424],{"class":271},",\n",[128,1426,1427,1429,1432],{"class":130,"line":455},[128,1428,105],{"class":267},[128,1430,1431],{"class":863},".primary-btn",[128,1433,1253],{"class":271},[128,1435,1436,1439,1441,1444,1446,1449,1452,1455],{"class":130,"line":464},[128,1437,1438],{"class":608},"    background-color",[128,1440,272],{"class":271},[128,1442,1443],{"class":608},"var",[128,1445,1266],{"class":271},[128,1447,1448],{"class":1380},"--brand-primary",[128,1450,1451],{"class":271},") ",[128,1453,1454],{"class":1285},"!important",[128,1456,1289],{"class":271},[128,1458,1459,1462,1464,1466,1468,1470,1472,1474],{"class":130,"line":469},[128,1460,1461],{"class":608},"    border-color",[128,1463,272],{"class":271},[128,1465,1443],{"class":608},[128,1467,1266],{"class":271},[128,1469,1448],{"class":1380},[128,1471,1451],{"class":271},[128,1473,1454],{"class":1285},[128,1475,1289],{"class":271},[128,1477,1478],{"class":130,"line":477},[128,1479,929],{"class":271},[128,1481,1482],{"class":130,"line":487},[128,1483,282],{"emptyLinePlaceholder":281},[128,1485,1486],{"class":130,"line":496},[128,1487,1488],{"class":1232},"\u002F* Favicon (limited - needs separate approach) *\u002F\n",[128,1490,1491],{"class":130,"line":503},[128,1492,1493],{"class":1232},"\u002F* See tips section below for favicon handling *\u002F\n",[1102,1495,1497],{"id":1496},"tips-favicon-og-image","⚡ Tips: Favicon & OG Image",[11,1499,1500,1502],{},[26,1501,28],{}," bisa inject favicon alternatif:",[119,1504,1506],{"className":900,"code":1505,"language":902,"meta":124,"style":124},"# Di dalam location block, tambahkan:\nsub_filter '\u003Clink rel=\"icon\"' '\u003Clink rel=\"icon\" href=\"https:\u002F\u002Fbookmarks.example.com\u002Ffavicon.ico\"';\n",[26,1507,1508,1513],{"__ignoreMap":124},[128,1509,1510],{"class":130,"line":131},[128,1511,1512],{},"# Di dalam location block, tambahkan:\n",[128,1514,1515],{"class":130,"line":137},[128,1516,1517],{},"sub_filter '\u003Clink rel=\"icon\"' '\u003Clink rel=\"icon\" href=\"https:\u002F\u002Fbookmarks.example.com\u002Ffavicon.ico\"';\n",[11,1519,1520],{},"Untuk OG image (preview di social media), ini biasanya meta tag — bisa juga di-sub_filter:",[119,1522,1524],{"className":900,"code":1523,"language":902,"meta":124,"style":124},"sub_filter '\u003Cmeta property=\"og:image\"' '\u003Cmeta property=\"og:image\" content=\"https:\u002F\u002Fbookmarks.example.com\u002Fog-image.jpg\"';\n",[26,1525,1526],{"__ignoreMap":124},[128,1527,1528],{"class":130,"line":131},[128,1529,1523],{},[1102,1531,1533],{"id":1532},"dark-mode-considerations","🌙 Dark Mode Considerations",[832,1535,1536],{},[11,1537,836,1538,1541],{},[18,1539,1540],{},"Warning:"," Jangan override CSS variables secara agresif di dark mode! Karakeep sudah punya dark mode bawaan yang cukup baik.",[11,1543,1544],{},"Tips untuk dark mode:",[119,1546,1548],{"className":1223,"code":1547,"language":1225,"meta":124,"style":124},"\u002F* Hanya override yang perlu, sisakan ke app default *\u002F\n@media (prefers-color-scheme: dark) {\n    \u002F* Cukup override brand color, jangan semua *\u002F\n    button.primary {\n        background-color: #818cf8 !important;\n    }\n}\n\n\u002F* JANGAN lakukan ini (anti-pattern): *\u002F\n\u002F* * { background: #000 !important; color: #fff !important; } *\u002F\n\u002F* Ini akan break UI dan overwrite user preference *\u002F\n",[26,1549,1550,1555,1563,1568,1577,1592,1596,1600,1604,1609,1614],{"__ignoreMap":124},[128,1551,1552],{"class":130,"line":131},[128,1553,1554],{"class":1232},"\u002F* Hanya override yang perlu, sisakan ke app default *\u002F\n",[128,1556,1557,1560],{"class":130,"line":137},[128,1558,1559],{"class":1285},"@media",[128,1561,1562],{"class":271}," (prefers-color-scheme: dark) {\n",[128,1564,1565],{"class":130,"line":143},[128,1566,1567],{"class":1232},"    \u002F* Cukup override brand color, jangan semua *\u002F\n",[128,1569,1570,1573,1575],{"class":130,"line":149},[128,1571,1572],{"class":267},"    button",[128,1574,1421],{"class":863},[128,1576,1253],{"class":271},[128,1578,1579,1582,1584,1587,1590],{"class":130,"line":155},[128,1580,1581],{"class":608},"        background-color",[128,1583,272],{"class":271},[128,1585,1586],{"class":608},"#818cf8",[128,1588,1589],{"class":1285}," !important",[128,1591,1289],{"class":271},[128,1593,1594],{"class":130,"line":161},[128,1595,1047],{"class":271},[128,1597,1598],{"class":130,"line":167},[128,1599,929],{"class":271},[128,1601,1602],{"class":130,"line":173},[128,1603,282],{"emptyLinePlaceholder":281},[128,1605,1606],{"class":130,"line":179},[128,1607,1608],{"class":1232},"\u002F* JANGAN lakukan ini (anti-pattern): *\u002F\n",[128,1610,1611],{"class":130,"line":185},[128,1612,1613],{"class":1232},"\u002F* * { background: #000 !important; color: #fff !important; } *\u002F\n",[128,1615,1616],{"class":130,"line":191},[128,1617,1618],{"class":1232},"\u002F* Ini akan break UI dan overwrite user preference *\u002F\n",[11,1620,1621],{},[18,1622,1623],{},"Best practice:",[39,1625,1626,1629,1632],{},[42,1627,1628],{},"Override minimal — logo, nama app, brand color saja",[42,1630,1631],{},"Biarkan dark\u002Flight mode toggle dari app yang handle",[42,1633,1634],{},"Test kedua mode setelah apply custom CSS",[31,1636,1638],{"id":1637},"verifikasi","✅ Verifikasi",[11,1640,1641],{},"Setelah semua setup, cek beberapa hal:",[119,1643,1645],{"className":854,"code":1644,"language":856,"meta":124,"style":124},"# 1. Cek Nginx config valid\nnginx -t\n\n# 2. Reload Nginx\nsystemctl reload nginx\n\n# 3. Test response header (pastikan tidak compressed)\ncurl -I https:\u002F\u002Fbookmarks.example.com\n\n# 4. Verify sub_filter working\ncurl -s https:\u002F\u002Fbookmarks.example.com | grep -i \"mymarks\"\n",[26,1646,1647,1652,1659,1663,1668,1679,1683,1688,1699,1703,1708],{"__ignoreMap":124},[128,1648,1649],{"class":130,"line":131},[128,1650,1651],{"class":1232},"# 1. Cek Nginx config valid\n",[128,1653,1654,1656],{"class":130,"line":137},[128,1655,902],{"class":863},[128,1657,1658],{"class":608}," -t\n",[128,1660,1661],{"class":130,"line":143},[128,1662,282],{"emptyLinePlaceholder":281},[128,1664,1665],{"class":130,"line":149},[128,1666,1667],{"class":1232},"# 2. Reload Nginx\n",[128,1669,1670,1673,1676],{"class":130,"line":155},[128,1671,1672],{"class":863},"systemctl",[128,1674,1675],{"class":275}," reload",[128,1677,1678],{"class":275}," nginx\n",[128,1680,1681],{"class":130,"line":161},[128,1682,282],{"emptyLinePlaceholder":281},[128,1684,1685],{"class":130,"line":167},[128,1686,1687],{"class":1232},"# 3. Test response header (pastikan tidak compressed)\n",[128,1689,1690,1693,1696],{"class":130,"line":173},[128,1691,1692],{"class":863},"curl",[128,1694,1695],{"class":608}," -I",[128,1697,1698],{"class":275}," https:\u002F\u002Fbookmarks.example.com\n",[128,1700,1701],{"class":130,"line":179},[128,1702,282],{"emptyLinePlaceholder":281},[128,1704,1705],{"class":130,"line":185},[128,1706,1707],{"class":1232},"# 4. Verify sub_filter working\n",[128,1709,1710,1712,1715,1718,1721,1724,1727],{"class":130,"line":191},[128,1711,1692],{"class":863},[128,1713,1714],{"class":608}," -s",[128,1716,1717],{"class":275}," https:\u002F\u002Fbookmarks.example.com",[128,1719,1720],{"class":1285}," |",[128,1722,1723],{"class":863}," grep",[128,1725,1726],{"class":608}," -i",[128,1728,1729],{"class":275}," \"mymarks\"\n",[11,1731,1732,1733,1736],{},"Kalau semuanya OK, buka ",[26,1734,1735],{},"https:\u002F\u002Fbookmarks.example.com"," di browser — kamu akan melihat branding custom \"MyMarks\" tanpa edit satu baris kode Karakeep.",[31,1738,1740],{"id":1739},"kesimpulan","🎯 Kesimpulan",[11,1742,1743],{},"Dengan setup ini kamu dapat:",[39,1745,1746,1749,1752,1755,1758,1761],{},[42,1747,1748],{},"✅ Bookmark manager self-hosted yang full-featured",[42,1750,1751],{},"✅ Custom branding tanpa fork atau edit source code",[42,1753,1754],{},"✅ Full-text search dengan Meilisearch",[42,1756,1757],{},"✅ AI auto-tagging support",[42,1759,1760],{},"✅ SSL via Let's Encrypt",[42,1762,1763],{},"✅ Mudah di-update (pull image baru, branding tetap karena di Nginx layer)",[11,1765,1766],{},[18,1767,1768,1769,255],{},"Keuntungan pendekatan ",[26,1770,28],{},[39,1772,1773,1776,1779,1782],{},[42,1774,1775],{},"Update Karakeep ke versi baru? Branding kamu tetap aman",[42,1777,1778],{},"Nggak perlu maintain fork",[42,1780,1781],{},"Bisa revert branding instant (hapus config Nginx)",[42,1783,1784],{},"Layer terpisah — app tetap clean, branding di proxy layer",[11,1786,1787,1788,1790],{},"Kalau kamu punya multiple self-hosted apps, pendekatan ini bisa di-reuse untuk semua — tinggal sesuaikan ",[26,1789,28],{}," rules masing-masing app.",[11,1792,1793],{},"Happy self-hosting! 🚀",[1795,1796,1797],"style",{},"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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":124,"searchDepth":137,"depth":137,"links":1799},[1800,1801,1802,1803,1804,1805,1811,1812],{"id":33,"depth":137,"text":34},{"id":74,"depth":137,"text":75},{"id":113,"depth":137,"text":114},{"id":247,"depth":137,"text":248},{"id":893,"depth":137,"text":894},{"id":1089,"depth":137,"text":1090,"children":1806},[1807,1808,1809,1810],{"id":1104,"depth":143,"text":1105},{"id":1213,"depth":143,"text":1214},{"id":1496,"depth":143,"text":1497},{"id":1532,"depth":143,"text":1533},{"id":1637,"depth":137,"text":1638},{"id":1739,"depth":137,"text":1740},"tech","2026-04-07","Tutorial setup Karakeep bookmark manager self-hosted dengan Docker, nginx reverse proxy, dan custom branding tanpa edit source code","md","\u002Fimages\u002Fposts\u002Fsimpen-bookmark-manager.jpg",{"slug":1819},"simpen-bookmark-manager","\u002Ftech\u002Fsimpen-bookmark-manager",null,{"title":5,"description":1815},"tech\u002Fsimpen-bookmark-manager",[1825,864,902,1826,1827],"self-hosted","bookmark","karakeep","N8sPGOHUiLMo7kvhUCGDexnUqlm1S_8QOzbdqwVJ9rQ",1775570219404]