[{"data":1,"prerenderedAt":6885},["ShallowReactive",2],{"validate-devops-hashicorp-vault-coolify-secret-manager":3,"blog/hashicorp-vault-coolify-secret-manager":504,"all-articles":818,"related-articles":6884},{"id":4,"title":5,"body":6,"category":465,"date":466,"description":467,"draft":468,"extension":469,"howto":470,"image":490,"meta":491,"navigation":117,"path":492,"seo":493,"stem":494,"tags":495,"__hash__":503},"blog/blog/17.hashicorp-vault-coolify-secret-manager.md","How to Configure HashiCorp Vault as a Secret Manager in Coolify",{"type":7,"value":8,"toc":458},"minimark",[9,13,16,26,29,32,37,40,61,68,72,83,179,185,196,200,206,214,217,286,293,303,306,310,324,352,362,366,377,404,407,413,426,429,440,442,445,454],[10,11,12],"p",{},"Coolify can keep environment variables for each resource. You can also keep the values in HashiCorp Vault. Then Coolify reads the values from Vault during deployment.",[10,14,15],{},"This method does two things:",[17,18,19,23],"ul",{},[20,21,22],"li",{},"You change a secret in one location.",[20,24,25],{},"The secret value is not in the Coolify database.",[10,27,28],{},"This article shows how to connect Vault to Coolify and how to use a Vault secret in an application.",[30,31],"hr",{},[33,34,36],"h2",{"id":35},"create-the-secret-in-vault","Create the secret in Vault",[10,38,39],{},"If you do not have a KV v2 secrets engine, enable one:",[17,41,42,53],{},[20,43,44,48,49],{},[45,46,47],"strong",{},"Mount:"," ",[50,51,52],"code",{},"kv",[20,54,55,48,58],{},[45,56,57],{},"Secret path:",[50,59,60],{},"coolify",[10,62,63,64,67],{},"Add the key-value pairs to ",[50,65,66],{},"kv/coolify",". Do not add the values to Coolify.",[33,69,71],{"id":70},"create-the-policy","Create the policy",[10,73,74,75,78,79,82],{},"Open ",[45,76,77],{},"Access → Policies"," and create an ACL policy with the name ",[50,80,81],{},"read_coolify",":",[84,85,90],"pre",{"className":86,"code":87,"language":88,"meta":89,"style":89},"language-hcl shiki shiki-themes github-light github-dark","path \"kv/data/*\" {\n  capabilities = [\"read\"]\n}\n\npath \"kv/metadata/*\" {\n  capabilities = [\"read\", \"list\"]\n}\n\npath \"auth/token/lookup-self\" {\n  capabilities = [\"read\"]\n}\n\npath \"auth/token/renew-self\" {\n  capabilities = [\"update\"]\n}\n","hcl","",[50,91,92,100,106,112,119,125,131,136,141,147,152,157,162,168,174],{"__ignoreMap":89},[93,94,97],"span",{"class":95,"line":96},"line",1,[93,98,99],{},"path \"kv/data/*\" {\n",[93,101,103],{"class":95,"line":102},2,[93,104,105],{},"  capabilities = [\"read\"]\n",[93,107,109],{"class":95,"line":108},3,[93,110,111],{},"}\n",[93,113,115],{"class":95,"line":114},4,[93,116,118],{"emptyLinePlaceholder":117},true,"\n",[93,120,122],{"class":95,"line":121},5,[93,123,124],{},"path \"kv/metadata/*\" {\n",[93,126,128],{"class":95,"line":127},6,[93,129,130],{},"  capabilities = [\"read\", \"list\"]\n",[93,132,134],{"class":95,"line":133},7,[93,135,111],{},[93,137,139],{"class":95,"line":138},8,[93,140,118],{"emptyLinePlaceholder":117},[93,142,144],{"class":95,"line":143},9,[93,145,146],{},"path \"auth/token/lookup-self\" {\n",[93,148,150],{"class":95,"line":149},10,[93,151,105],{},[93,153,155],{"class":95,"line":154},11,[93,156,111],{},[93,158,160],{"class":95,"line":159},12,[93,161,118],{"emptyLinePlaceholder":117},[93,163,165],{"class":95,"line":164},13,[93,166,167],{},"path \"auth/token/renew-self\" {\n",[93,169,171],{"class":95,"line":170},14,[93,172,173],{},"  capabilities = [\"update\"]\n",[93,175,177],{"class":95,"line":176},15,[93,178,111],{},[10,180,181,182,184],{},"This policy allows read access to all secrets in the ",[50,183,52],{}," mount. If other users can access the Vault, limit the paths to one secret.",[10,186,187,188,191,192,195],{},"The token does not include the default policy. For this reason, Coolify cannot validate the token without the ",[50,189,190],{},"lookup-self"," rule. The ",[50,193,194],{},"renew-self"," rule is only for renewable tokens. You can remove it for this procedure.",[33,197,199],{"id":198},"create-the-token","Create the token",[10,201,74,202,205],{},[45,203,204],{},"Operational Tools → API Explorer"," and send this request:",[84,207,212],{"className":208,"code":210,"language":211,"meta":89},[209],"language-text","POST /v1/auth/token/create\n","text",[50,213,210],{"__ignoreMap":89},[10,215,216],{},"Use this request body:",[84,218,222],{"className":219,"code":220,"language":221,"meta":89,"style":89},"language-json shiki shiki-themes github-light github-dark","{\n  \"policies\": [\"read_coolify\"],\n  \"ttl\": \"768h\",\n  \"renewable\": false,\n  \"no_default_policy\": true\n}\n","json",[50,223,224,230,246,260,272,282],{"__ignoreMap":89},[93,225,226],{"class":95,"line":96},[93,227,229],{"class":228},"sVt8B","{\n",[93,231,232,236,239,243],{"class":95,"line":102},[93,233,235],{"class":234},"sj4cs","  \"policies\"",[93,237,238],{"class":228},": [",[93,240,242],{"class":241},"sZZnC","\"read_coolify\"",[93,244,245],{"class":228},"],\n",[93,247,248,251,254,257],{"class":95,"line":108},[93,249,250],{"class":234},"  \"ttl\"",[93,252,253],{"class":228},": ",[93,255,256],{"class":241},"\"768h\"",[93,258,259],{"class":228},",\n",[93,261,262,265,267,270],{"class":95,"line":114},[93,263,264],{"class":234},"  \"renewable\"",[93,266,253],{"class":228},[93,268,269],{"class":234},"false",[93,271,259],{"class":228},[93,273,274,277,279],{"class":95,"line":121},[93,275,276],{"class":234},"  \"no_default_policy\"",[93,278,253],{"class":228},[93,280,281],{"class":234},"true\n",[93,283,284],{"class":95,"line":127},[93,285,111],{"class":228},[10,287,288,289,292],{},"Copy ",[50,290,291],{},"auth.client_token"," from the response. Vault shows this value one time only.",[10,294,295,298,299,302],{},[50,296,297],{},"768h"," is 32 days. Vault can set a shorter lifetime if ",[50,300,301],{},"max_ttl"," is lower. Read the token lifetime in the response.",[10,304,305],{},"The token is not renewable. When the token expires, deployments that use the secret fail. Set a reminder before the expiration date.",[33,307,309],{"id":308},"add-the-integration-in-coolify","Add the integration in Coolify",[10,311,74,312,315,316,319,320,323],{},[45,313,314],{},"Keys & Tokens → Integration Tokens"," and select ",[45,317,318],{},"New token",". Select ",[45,321,322],{},"HashiCorp Vault"," as the provider.",[17,325,326,340,346],{},[20,327,328,331,332,335,336,339],{},[45,329,330],{},"Base URL:"," the Vault URL, for example ",[50,333,334],{},"https://vault.example.com",". Do not add ",[50,337,338],{},"/v1",".",[20,341,342,345],{},[45,343,344],{},"Vault token:"," the token from the previous step.",[20,347,348,351],{},[45,349,350],{},"Namespace:"," only for Vault Enterprise or HCP Vault.",[10,353,354,355,358,359,361],{},"Select ",[45,356,357],{},"Validate and add",". If the validation fails, check the policy. Make sure that the policy contains the ",[50,360,190],{}," rule. Also, the token can be older than the policy.",[33,363,365],{"id":364},"use-the-secret-in-a-resource","Use the secret in a resource",[10,367,368,369,372,373,376],{},"Open the application and select ",[45,370,371],{},"Environment Variables",". In ",[45,374,375],{},"Secret manager",", select the integration.",[378,379,380,389,396,401],"ol",{},[20,381,382,383,386,387,339],{},"Set ",[45,384,385],{},"Mount"," to ",[50,388,52],{},[20,390,382,391,386,394,339],{},[45,392,393],{},"Secret path",[50,395,60],{},[20,397,354,398,339],{},[45,399,400],{},"Browse keys",[20,402,403],{},"Add the keys that you need, or import all keys.",[10,405,406],{},"Coolify creates a reference for each key:",[84,408,411],{"className":409,"code":410,"language":211,"meta":89},[209],"API_KEY={{vault.API_KEY}}\n",[50,412,410],{"__ignoreMap":89},[10,414,415,416,418,419,421,422,425],{},"Use ",[50,417,52],{}," as the mount and ",[50,420,60],{}," as the path. Do not use ",[50,423,424],{},"/v1/kv/data/coolify",". That value is an HTTP API path.",[10,427,428],{},"Note these points:",[17,430,431,434,437],{},[20,432,433],{},"Coolify reads the values during deployment or restart. Coolify does not keep the values in its database. Coolify does not show the values in the deployment log.",[20,435,436],{},"If you change a value in Vault, deploy or restart the resource again. For runtime variables, do a restart. For build-time variables, do a full deployment.",[20,438,439],{},"One resource can use one secret manager source.",[30,441],{},[10,443,444],{},"This procedure takes approximately 20 minutes. After the configuration, you change a secret one time in Vault and deploy the resource again.",[446,447,448],"blockquote",{},[10,449,450,453],{},[45,451,452],{},"Note:"," This article was dictated and refined with the help of AI.",[455,456,457],"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 .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":89,"searchDepth":102,"depth":102,"links":459},[460,461,462,463,464],{"id":35,"depth":102,"text":36},{"id":70,"depth":102,"text":71},{"id":198,"depth":102,"text":199},{"id":308,"depth":102,"text":309},{"id":364,"depth":102,"text":365},"devops","2026-11-02","This article shows how to connect HashiCorp Vault to Coolify. It shows how to create a KV v2 secret, a read-only policy, and a token. It also shows how to use the secret in an application.",false,"md",{"totalTime":471,"tool":472,"steps":475},"PT20M",[473,474],"HashiCorp Vault instance","Coolify instance",[476,478,481,483,485,487],{"name":36,"text":477},"Enable a KV v2 secrets engine with the mount kv. Create a secret at the path coolify and add the key-value pairs.",{"name":479,"text":480},"Create the read policy","Open Access > Policies and create an ACL policy with the name read_coolify. Add the HCL from this article.",{"name":199,"text":482},"Open Operational Tools > API Explorer. Send POST /v1/auth/token/create with the read_coolify policy, a TTL of 768h, no default policy, and renewable set to false.",{"name":309,"text":484},"Open Keys & Tokens > Integration Tokens and create a HashiCorp Vault token. Add the Vault base URL and the token, then select Validate and add.",{"name":365,"text":486},"Open the application Environment Variables and select the integration in Secret manager. Set Mount to kv and Secret path to coolify, then add the keys.",{"name":488,"text":489},"Deploy the resource","Deploy or restart the resource and make sure that the variables have values. If you change a value in Vault, deploy the resource again.",null,{"lastmod":466,"external":468},"/blog/hashicorp-vault-coolify-secret-manager",{"title":5,"description":467},"blog/17.hashicorp-vault-coolify-secret-manager",[60,496,497,498,499,500,501,502],"vault","hashicorp","secrets","security","selfhosted","homelab","tinkering","LvgwDReZwBUo_CIEZGsL8Lu8x9V1mATNo6y4IMDd0XY",{"id":4,"title":5,"body":505,"category":465,"date":466,"description":467,"draft":468,"extension":469,"howto":806,"image":490,"meta":815,"navigation":117,"path":492,"seo":816,"stem":494,"tags":817,"__hash__":503},{"type":7,"value":506,"toc":799},[507,509,511,517,519,521,523,525,539,543,545,551,615,619,625,627,631,636,638,688,692,698,700,702,710,728,734,736,742,762,764,769,777,779,787,789,791,797],[10,508,12],{},[10,510,15],{},[17,512,513,515],{},[20,514,22],{},[20,516,25],{},[10,518,28],{},[30,520],{},[33,522,36],{"id":35},[10,524,39],{},[17,526,527,533],{},[20,528,529,48,531],{},[45,530,47],{},[50,532,52],{},[20,534,535,48,537],{},[45,536,57],{},[50,538,60],{},[10,540,63,541,67],{},[50,542,66],{},[33,544,71],{"id":70},[10,546,74,547,78,549,82],{},[45,548,77],{},[50,550,81],{},[84,552,553],{"className":86,"code":87,"language":88,"meta":89,"style":89},[50,554,555,559,563,567,571,575,579,583,587,591,595,599,603,607,611],{"__ignoreMap":89},[93,556,557],{"class":95,"line":96},[93,558,99],{},[93,560,561],{"class":95,"line":102},[93,562,105],{},[93,564,565],{"class":95,"line":108},[93,566,111],{},[93,568,569],{"class":95,"line":114},[93,570,118],{"emptyLinePlaceholder":117},[93,572,573],{"class":95,"line":121},[93,574,124],{},[93,576,577],{"class":95,"line":127},[93,578,130],{},[93,580,581],{"class":95,"line":133},[93,582,111],{},[93,584,585],{"class":95,"line":138},[93,586,118],{"emptyLinePlaceholder":117},[93,588,589],{"class":95,"line":143},[93,590,146],{},[93,592,593],{"class":95,"line":149},[93,594,105],{},[93,596,597],{"class":95,"line":154},[93,598,111],{},[93,600,601],{"class":95,"line":159},[93,602,118],{"emptyLinePlaceholder":117},[93,604,605],{"class":95,"line":164},[93,606,167],{},[93,608,609],{"class":95,"line":170},[93,610,173],{},[93,612,613],{"class":95,"line":176},[93,614,111],{},[10,616,181,617,184],{},[50,618,52],{},[10,620,187,621,191,623,195],{},[50,622,190],{},[50,624,194],{},[33,626,199],{"id":198},[10,628,74,629,205],{},[45,630,204],{},[84,632,634],{"className":633,"code":210,"language":211,"meta":89},[209],[50,635,210],{"__ignoreMap":89},[10,637,216],{},[84,639,640],{"className":219,"code":220,"language":221,"meta":89,"style":89},[50,641,642,646,656,666,676,684],{"__ignoreMap":89},[93,643,644],{"class":95,"line":96},[93,645,229],{"class":228},[93,647,648,650,652,654],{"class":95,"line":102},[93,649,235],{"class":234},[93,651,238],{"class":228},[93,653,242],{"class":241},[93,655,245],{"class":228},[93,657,658,660,662,664],{"class":95,"line":108},[93,659,250],{"class":234},[93,661,253],{"class":228},[93,663,256],{"class":241},[93,665,259],{"class":228},[93,667,668,670,672,674],{"class":95,"line":114},[93,669,264],{"class":234},[93,671,253],{"class":228},[93,673,269],{"class":234},[93,675,259],{"class":228},[93,677,678,680,682],{"class":95,"line":121},[93,679,276],{"class":234},[93,681,253],{"class":228},[93,683,281],{"class":234},[93,685,686],{"class":95,"line":127},[93,687,111],{"class":228},[10,689,288,690,292],{},[50,691,291],{},[10,693,694,298,696,302],{},[50,695,297],{},[50,697,301],{},[10,699,305],{},[33,701,309],{"id":308},[10,703,74,704,315,706,319,708,323],{},[45,705,314],{},[45,707,318],{},[45,709,322],{},[17,711,712,720,724],{},[20,713,714,331,716,335,718,339],{},[45,715,330],{},[50,717,334],{},[50,719,338],{},[20,721,722,345],{},[45,723,344],{},[20,725,726,351],{},[45,727,350],{},[10,729,354,730,358,732,361],{},[45,731,357],{},[50,733,190],{},[33,735,365],{"id":364},[10,737,368,738,372,740,376],{},[45,739,371],{},[45,741,375],{},[378,743,744,750,756,760],{},[20,745,382,746,386,748,339],{},[45,747,385],{},[50,749,52],{},[20,751,382,752,386,754,339],{},[45,753,393],{},[50,755,60],{},[20,757,354,758,339],{},[45,759,400],{},[20,761,403],{},[10,763,406],{},[84,765,767],{"className":766,"code":410,"language":211,"meta":89},[209],[50,768,410],{"__ignoreMap":89},[10,770,415,771,418,773,421,775,425],{},[50,772,52],{},[50,774,60],{},[50,776,424],{},[10,778,428],{},[17,780,781,783,785],{},[20,782,433],{},[20,784,436],{},[20,786,439],{},[30,788],{},[10,790,444],{},[446,792,793],{},[10,794,795,453],{},[45,796,452],{},[455,798,457],{},{"title":89,"searchDepth":102,"depth":102,"links":800},[801,802,803,804,805],{"id":35,"depth":102,"text":36},{"id":70,"depth":102,"text":71},{"id":198,"depth":102,"text":199},{"id":308,"depth":102,"text":309},{"id":364,"depth":102,"text":365},{"totalTime":471,"tool":807,"steps":808},[473,474],[809,810,811,812,813,814],{"name":36,"text":477},{"name":479,"text":480},{"name":199,"text":482},{"name":309,"text":484},{"name":365,"text":486},{"name":488,"text":489},{"lastmod":466,"external":468},{"title":5,"description":467},[60,496,497,498,499,500,501,502],[819,1133,2340,2905,3186,3330,3432,3528,4061,4813,5388,6328,6801,6849],{"id":4,"title":5,"body":820,"category":465,"date":466,"description":467,"draft":468,"extension":469,"howto":1121,"image":490,"meta":1130,"navigation":117,"path":492,"seo":1131,"stem":494,"tags":1132,"__hash__":503},{"type":7,"value":821,"toc":1114},[822,824,826,832,834,836,838,840,854,858,860,866,930,934,940,942,946,951,953,1003,1007,1013,1015,1017,1025,1043,1049,1051,1057,1077,1079,1084,1092,1094,1102,1104,1106,1112],[10,823,12],{},[10,825,15],{},[17,827,828,830],{},[20,829,22],{},[20,831,25],{},[10,833,28],{},[30,835],{},[33,837,36],{"id":35},[10,839,39],{},[17,841,842,848],{},[20,843,844,48,846],{},[45,845,47],{},[50,847,52],{},[20,849,850,48,852],{},[45,851,57],{},[50,853,60],{},[10,855,63,856,67],{},[50,857,66],{},[33,859,71],{"id":70},[10,861,74,862,78,864,82],{},[45,863,77],{},[50,865,81],{},[84,867,868],{"className":86,"code":87,"language":88,"meta":89,"style":89},[50,869,870,874,878,882,886,890,894,898,902,906,910,914,918,922,926],{"__ignoreMap":89},[93,871,872],{"class":95,"line":96},[93,873,99],{},[93,875,876],{"class":95,"line":102},[93,877,105],{},[93,879,880],{"class":95,"line":108},[93,881,111],{},[93,883,884],{"class":95,"line":114},[93,885,118],{"emptyLinePlaceholder":117},[93,887,888],{"class":95,"line":121},[93,889,124],{},[93,891,892],{"class":95,"line":127},[93,893,130],{},[93,895,896],{"class":95,"line":133},[93,897,111],{},[93,899,900],{"class":95,"line":138},[93,901,118],{"emptyLinePlaceholder":117},[93,903,904],{"class":95,"line":143},[93,905,146],{},[93,907,908],{"class":95,"line":149},[93,909,105],{},[93,911,912],{"class":95,"line":154},[93,913,111],{},[93,915,916],{"class":95,"line":159},[93,917,118],{"emptyLinePlaceholder":117},[93,919,920],{"class":95,"line":164},[93,921,167],{},[93,923,924],{"class":95,"line":170},[93,925,173],{},[93,927,928],{"class":95,"line":176},[93,929,111],{},[10,931,181,932,184],{},[50,933,52],{},[10,935,187,936,191,938,195],{},[50,937,190],{},[50,939,194],{},[33,941,199],{"id":198},[10,943,74,944,205],{},[45,945,204],{},[84,947,949],{"className":948,"code":210,"language":211,"meta":89},[209],[50,950,210],{"__ignoreMap":89},[10,952,216],{},[84,954,955],{"className":219,"code":220,"language":221,"meta":89,"style":89},[50,956,957,961,971,981,991,999],{"__ignoreMap":89},[93,958,959],{"class":95,"line":96},[93,960,229],{"class":228},[93,962,963,965,967,969],{"class":95,"line":102},[93,964,235],{"class":234},[93,966,238],{"class":228},[93,968,242],{"class":241},[93,970,245],{"class":228},[93,972,973,975,977,979],{"class":95,"line":108},[93,974,250],{"class":234},[93,976,253],{"class":228},[93,978,256],{"class":241},[93,980,259],{"class":228},[93,982,983,985,987,989],{"class":95,"line":114},[93,984,264],{"class":234},[93,986,253],{"class":228},[93,988,269],{"class":234},[93,990,259],{"class":228},[93,992,993,995,997],{"class":95,"line":121},[93,994,276],{"class":234},[93,996,253],{"class":228},[93,998,281],{"class":234},[93,1000,1001],{"class":95,"line":127},[93,1002,111],{"class":228},[10,1004,288,1005,292],{},[50,1006,291],{},[10,1008,1009,298,1011,302],{},[50,1010,297],{},[50,1012,301],{},[10,1014,305],{},[33,1016,309],{"id":308},[10,1018,74,1019,315,1021,319,1023,323],{},[45,1020,314],{},[45,1022,318],{},[45,1024,322],{},[17,1026,1027,1035,1039],{},[20,1028,1029,331,1031,335,1033,339],{},[45,1030,330],{},[50,1032,334],{},[50,1034,338],{},[20,1036,1037,345],{},[45,1038,344],{},[20,1040,1041,351],{},[45,1042,350],{},[10,1044,354,1045,358,1047,361],{},[45,1046,357],{},[50,1048,190],{},[33,1050,365],{"id":364},[10,1052,368,1053,372,1055,376],{},[45,1054,371],{},[45,1056,375],{},[378,1058,1059,1065,1071,1075],{},[20,1060,382,1061,386,1063,339],{},[45,1062,385],{},[50,1064,52],{},[20,1066,382,1067,386,1069,339],{},[45,1068,393],{},[50,1070,60],{},[20,1072,354,1073,339],{},[45,1074,400],{},[20,1076,403],{},[10,1078,406],{},[84,1080,1082],{"className":1081,"code":410,"language":211,"meta":89},[209],[50,1083,410],{"__ignoreMap":89},[10,1085,415,1086,418,1088,421,1090,425],{},[50,1087,52],{},[50,1089,60],{},[50,1091,424],{},[10,1093,428],{},[17,1095,1096,1098,1100],{},[20,1097,433],{},[20,1099,436],{},[20,1101,439],{},[30,1103],{},[10,1105,444],{},[446,1107,1108],{},[10,1109,1110,453],{},[45,1111,452],{},[455,1113,457],{},{"title":89,"searchDepth":102,"depth":102,"links":1115},[1116,1117,1118,1119,1120],{"id":35,"depth":102,"text":36},{"id":70,"depth":102,"text":71},{"id":198,"depth":102,"text":199},{"id":308,"depth":102,"text":309},{"id":364,"depth":102,"text":365},{"totalTime":471,"tool":1122,"steps":1123},[473,474],[1124,1125,1126,1127,1128,1129],{"name":36,"text":477},{"name":479,"text":480},{"name":199,"text":482},{"name":309,"text":484},{"name":365,"text":486},{"name":488,"text":489},{"lastmod":466,"external":468},{"title":5,"description":467},[60,496,497,498,499,500,501,502],{"id":1134,"title":1135,"body":1136,"category":501,"date":2298,"description":2299,"draft":468,"extension":469,"howto":2300,"image":490,"meta":2331,"navigation":117,"path":2332,"seo":2333,"stem":2334,"tags":2335,"__hash__":2339},"blog/blog/16.plex-gpu-transcoding-nvidia-docker.md","Plex GPU Transcoding with Nvidia in Docker - The Right Way",{"type":7,"value":1137,"toc":2276},[1138,1147,1150,1153,1156,1159,1170,1177,1182,1184,1188,1191,1194,1202,1212,1221,1224,1226,1230,1233,1257,1259,1263,1268,1271,1322,1332,1336,1342,1398,1401,1419,1423,1426,1476,1482,1484,1488,1496,1782,1785,1789,1795,1867,1870,1883,1897,1899,1903,1906,1987,1998,2000,2004,2007,2035,2038,2040,2044,2047,2066,2069,2092,2099,2102,2104,2108,2112,2123,2132,2136,2139,2155,2159,2165,2168,2183,2185,2189,2220,2223,2225,2228,2231,2234,2236,2240,2243,2271,2273],[10,1139,1140,1141,1146],{},"I've been deep into self-hosting for about 5 years now, and Plex has always been the crown jewel of my setup. My old rig was this tiny mini PC with an Intel I3 (4 cores) and 16GB of RAM, running everything through Proxmox - two VMs, one handling all my ",[1142,1143,1145],"a",{"href":1144},"/homelab/umami-portainer-traefik","Docker containers"," including Plex. Worked like a charm for what I needed.",[10,1148,1149],{},"But last year I finally treated myself to a proper PC with an Nvidia GPU. I knew I was probably signing up for some Linux driver headaches, especially since I had my heart set on a Hyprland setup. Still, the promise of hardware transcoding was too tempting.",[10,1151,1152],{},"That's when the real fun began.",[10,1154,1155],{},"I'd been running Plex in Docker for ages - honestly, it's the only sane way to manage it. But getting that shiny new GPU to actually do transcoding work? That nearly drove me to tears.",[10,1157,1158],{},"The old Intel setup just chugged along with CPU transcoding, and I didn't think much of it. But now I had this powerful GPU sitting there doing absolutely nothing while my CPU was still melting trying to transcode a 4K movie to my phone.",[10,1160,1161,1162,1165,1166,1169],{},"I wasted several evenings following guides that told me to use ",[50,1163,1164],{},"privileged: true"," or manually mount ",[50,1167,1168],{},"/dev/nvidia*"," devices. Half didn't work at all, the other half were basically giving root access to everything. Great security model there.",[10,1171,1172,1173,1176],{},"The worst part? The GPU would show up inside the container - you'd run ",[50,1174,1175],{},"nvidia-smi"," and think \"awesome, it's there!\" - but Plex would still be grinding away with CPU transcoding like it was 2010.",[446,1178,1179],{},[10,1180,1181],{},"Running this on Arch with Hyprland because I apparently enjoy making my life harder. The method should work, I guess, on any Linux distro where you can get proper Nvidia drivers running though - Ubuntu users, you're not missing out on the pain.",[30,1183],{},[33,1185,1187],{"id":1186},"what-was-actually-going-wrong","What was actually going wrong",[10,1189,1190],{},"Coming from my old Intel setup where CPU transcoding \"just worked\" (if you can call waiting a bit for a stream to start \"working\"), I thought GPU transcoding would be straightforward. Boy, was I wrong.",[10,1192,1193],{},"After banging my head against this for way too long, I realized most tutorials fall into the same traps:",[10,1195,1196,1201],{},[45,1197,1198,1199],{},"Option 1: ",[50,1200,1164],{}," - Sure, you get all the device files, but the Nvidia userspace libraries? Nowhere to be found.",[10,1203,1204,1207,1208,1211],{},[45,1205,1206],{},"Option 2: Manual device mounting"," - Same problem, except now you're also hardcoding device paths like ",[50,1209,1210],{},"/dev/nvidia0",". What happens when you add another GPU? Exactly.",[10,1213,1214,1215,1217,1218,1220],{},"Both approaches give you a container where ",[50,1216,1168],{}," exists and ",[50,1219,1175],{}," runs, but Plex can't actually talk to the GPU because the driver libraries aren't there. It's like having a Ferrari in your garage but the keys are locked inside the house.",[10,1222,1223],{},"Going from my reliable old Intel setup to this was frustrating - I went from \"slow but works\" to \"fast hardware but completely broken.\"",[30,1225],{},[33,1227,1229],{"id":1228},"the-solution-that-finally-worked","The solution that finally worked",[10,1231,1232],{},"After diving way too deep into Docker's GPU support documentation (and seriously considering going back to my old Intel setup), I figured out you need exactly three things:",[378,1234,1235,1241,1250],{},[20,1236,1237,1240],{},[45,1238,1239],{},"Nvidia Container Toolkit"," installed and properly configured (this is the important bit)",[20,1242,1243,1249],{},[45,1244,1245,1246],{},"Docker's ",[50,1247,1248],{},"deploy.resources.reservations"," syntax for GPU passthrough",[20,1251,1252,1253,1256],{},"The right ",[45,1254,1255],{},"environment variables"," in your container",[30,1258],{},[33,1260,1262],{"id":1261},"getting-your-system-ready","Getting your system ready",[1264,1265,1267],"h3",{"id":1266},"install-the-packages","Install the packages",[10,1269,1270],{},"On Arch (because of course):",[84,1272,1276],{"className":1273,"code":1274,"language":1275,"meta":89,"style":89},"language-bash shiki shiki-themes github-light github-dark","# Nvidia drivers - skip this if you already have them\nsudo pacman -S nvidia nvidia-utils\n\n# The magic sauce\nsudo pacman -S nvidia-container-toolkit\n","bash",[50,1277,1278,1284,1302,1306,1311],{"__ignoreMap":89},[93,1279,1280],{"class":95,"line":96},[93,1281,1283],{"class":1282},"sJ8bj","# Nvidia drivers - skip this if you already have them\n",[93,1285,1286,1290,1293,1296,1299],{"class":95,"line":102},[93,1287,1289],{"class":1288},"sScJk","sudo",[93,1291,1292],{"class":241}," pacman",[93,1294,1295],{"class":234}," -S",[93,1297,1298],{"class":241}," nvidia",[93,1300,1301],{"class":241}," nvidia-utils\n",[93,1303,1304],{"class":95,"line":108},[93,1305,118],{"emptyLinePlaceholder":117},[93,1307,1308],{"class":95,"line":114},[93,1309,1310],{"class":1282},"# The magic sauce\n",[93,1312,1313,1315,1317,1319],{"class":95,"line":121},[93,1314,1289],{"class":1288},[93,1316,1292],{"class":241},[93,1318,1295],{"class":234},[93,1320,1321],{"class":241}," nvidia-container-toolkit\n",[10,1323,1324,1325,1331],{},"Check out the ",[1142,1326,1330],{"href":1327,"rel":1328},"https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html",[1329],"nofollow","official Nvidia Container Toolkit docs",". They've got instructions for pretty much every package manager.",[1264,1333,1335],{"id":1334},"make-docker-play-nice-with-nvidia","Make Docker play nice with Nvidia",[10,1337,1338,1339,82],{},"You need to tell Docker about the Nvidia runtime. Edit or create ",[50,1340,1341],{},"/etc/docker/daemon.json",[84,1343,1345],{"className":219,"code":1344,"language":221,"meta":89,"style":89},"{\n    \"runtimes\": {\n        \"nvidia\": {\n            \"args\": [],\n            \"path\": \"nvidia-container-runtime\"\n        }\n    }\n}\n",[50,1346,1347,1351,1359,1366,1374,1384,1389,1394],{"__ignoreMap":89},[93,1348,1349],{"class":95,"line":96},[93,1350,229],{"class":228},[93,1352,1353,1356],{"class":95,"line":102},[93,1354,1355],{"class":234},"    \"runtimes\"",[93,1357,1358],{"class":228},": {\n",[93,1360,1361,1364],{"class":95,"line":108},[93,1362,1363],{"class":234},"        \"nvidia\"",[93,1365,1358],{"class":228},[93,1367,1368,1371],{"class":95,"line":114},[93,1369,1370],{"class":234},"            \"args\"",[93,1372,1373],{"class":228},": [],\n",[93,1375,1376,1379,1381],{"class":95,"line":121},[93,1377,1378],{"class":234},"            \"path\"",[93,1380,253],{"class":228},[93,1382,1383],{"class":241},"\"nvidia-container-runtime\"\n",[93,1385,1386],{"class":95,"line":127},[93,1387,1388],{"class":228},"        }\n",[93,1390,1391],{"class":95,"line":133},[93,1392,1393],{"class":228},"    }\n",[93,1395,1396],{"class":95,"line":138},[93,1397,111],{"class":228},[10,1399,1400],{},"Restart Docker (yeah, I know, restarting services is annoying):",[84,1402,1404],{"className":1273,"code":1403,"language":1275,"meta":89,"style":89},"sudo systemctl restart docker\n",[50,1405,1406],{"__ignoreMap":89},[93,1407,1408,1410,1413,1416],{"class":95,"line":96},[93,1409,1289],{"class":1288},[93,1411,1412],{"class":241}," systemctl",[93,1414,1415],{"class":241}," restart",[93,1417,1418],{"class":241}," docker\n",[1264,1420,1422],{"id":1421},"quick-sanity-check","Quick sanity check",[10,1424,1425],{},"Before we go any further, make sure everything's working:",[84,1427,1429],{"className":1273,"code":1428,"language":1275,"meta":89,"style":89},"# Check if Docker knows about the Nvidia runtime\ndocker info | grep -i runtime\n# You want to see: Runtimes: nvidia runc io.containerd.runc.v2\n\n# Make sure your GPU is still alive\nnvidia-smi\n",[50,1430,1431,1436,1457,1462,1466,1471],{"__ignoreMap":89},[93,1432,1433],{"class":95,"line":96},[93,1434,1435],{"class":1282},"# Check if Docker knows about the Nvidia runtime\n",[93,1437,1438,1441,1444,1448,1451,1454],{"class":95,"line":102},[93,1439,1440],{"class":1288},"docker",[93,1442,1443],{"class":241}," info",[93,1445,1447],{"class":1446},"szBVR"," |",[93,1449,1450],{"class":1288}," grep",[93,1452,1453],{"class":234}," -i",[93,1455,1456],{"class":241}," runtime\n",[93,1458,1459],{"class":95,"line":108},[93,1460,1461],{"class":1282},"# You want to see: Runtimes: nvidia runc io.containerd.runc.v2\n",[93,1463,1464],{"class":95,"line":114},[93,1465,118],{"emptyLinePlaceholder":117},[93,1467,1468],{"class":95,"line":121},[93,1469,1470],{"class":1282},"# Make sure your GPU is still alive\n",[93,1472,1473],{"class":95,"line":127},[93,1474,1475],{"class":1288},"nvidia-smi\n",[10,1477,1478,1479,1481],{},"If ",[50,1480,1175],{}," isn't working, fix that first. No point in debugging Docker if your drivers are broken.",[30,1483],{},[33,1485,1487],{"id":1486},"the-docker-compose-setup","The Docker Compose setup",[10,1489,1490,1491,1495],{},"Alright, here's the configuration that finally worked. I'm using the ",[45,1492,1493],{},[50,1494,1248],{}," approach because it's the modern way to do this:",[84,1497,1501],{"className":1498,"code":1499,"language":1500,"meta":89,"style":89},"language-yaml shiki shiki-themes github-light github-dark","plex:\n  image: lscr.io/linuxserver/plex:latest\n  container_name: plex\n  restart: unless-stopped\n  ports:\n    - \"32400:32400/tcp\"\n    - \"3005:3005/tcp\"\n    - \"8324:8324/tcp\"\n    - \"32469:32469/tcp\"\n    - \"1900:1900/udp\"\n    - \"32410:32410/udp\"\n    - \"32412:32412/udp\"\n    - \"32413:32413/udp\"\n    - \"32414:32414/udp\"\n  volumes:\n    - plex:/config\n    - /path/to/media:/media\n    - plex-transcode:/transcode\n  environment:\n    - TZ=Europe/Bucharest\n    - PUID=0\n    - PGID=0\n    - VERSION=docker\n    - NVIDIA_VISIBLE_DEVICES=all\n    - NVIDIA_DRIVER_CAPABILITIES=video,compute,utility\n  deploy:\n    resources:\n      reservations:\n        devices:\n          - driver: nvidia\n            count: all\n            capabilities: [video,compute,utility]\n","yaml",[50,1502,1503,1512,1522,1532,1542,1549,1557,1564,1571,1578,1585,1592,1599,1606,1613,1620,1628,1636,1644,1652,1660,1668,1676,1684,1692,1700,1708,1716,1724,1732,1746,1757],{"__ignoreMap":89},[93,1504,1505,1509],{"class":95,"line":96},[93,1506,1508],{"class":1507},"s9eBZ","plex",[93,1510,1511],{"class":228},":\n",[93,1513,1514,1517,1519],{"class":95,"line":102},[93,1515,1516],{"class":1507},"  image",[93,1518,253],{"class":228},[93,1520,1521],{"class":241},"lscr.io/linuxserver/plex:latest\n",[93,1523,1524,1527,1529],{"class":95,"line":108},[93,1525,1526],{"class":1507},"  container_name",[93,1528,253],{"class":228},[93,1530,1531],{"class":241},"plex\n",[93,1533,1534,1537,1539],{"class":95,"line":114},[93,1535,1536],{"class":1507},"  restart",[93,1538,253],{"class":228},[93,1540,1541],{"class":241},"unless-stopped\n",[93,1543,1544,1547],{"class":95,"line":121},[93,1545,1546],{"class":1507},"  ports",[93,1548,1511],{"class":228},[93,1550,1551,1554],{"class":95,"line":127},[93,1552,1553],{"class":228},"    - ",[93,1555,1556],{"class":241},"\"32400:32400/tcp\"\n",[93,1558,1559,1561],{"class":95,"line":133},[93,1560,1553],{"class":228},[93,1562,1563],{"class":241},"\"3005:3005/tcp\"\n",[93,1565,1566,1568],{"class":95,"line":138},[93,1567,1553],{"class":228},[93,1569,1570],{"class":241},"\"8324:8324/tcp\"\n",[93,1572,1573,1575],{"class":95,"line":143},[93,1574,1553],{"class":228},[93,1576,1577],{"class":241},"\"32469:32469/tcp\"\n",[93,1579,1580,1582],{"class":95,"line":149},[93,1581,1553],{"class":228},[93,1583,1584],{"class":241},"\"1900:1900/udp\"\n",[93,1586,1587,1589],{"class":95,"line":154},[93,1588,1553],{"class":228},[93,1590,1591],{"class":241},"\"32410:32410/udp\"\n",[93,1593,1594,1596],{"class":95,"line":159},[93,1595,1553],{"class":228},[93,1597,1598],{"class":241},"\"32412:32412/udp\"\n",[93,1600,1601,1603],{"class":95,"line":164},[93,1602,1553],{"class":228},[93,1604,1605],{"class":241},"\"32413:32413/udp\"\n",[93,1607,1608,1610],{"class":95,"line":170},[93,1609,1553],{"class":228},[93,1611,1612],{"class":241},"\"32414:32414/udp\"\n",[93,1614,1615,1618],{"class":95,"line":176},[93,1616,1617],{"class":1507},"  volumes",[93,1619,1511],{"class":228},[93,1621,1623,1625],{"class":95,"line":1622},16,[93,1624,1553],{"class":228},[93,1626,1627],{"class":241},"plex:/config\n",[93,1629,1631,1633],{"class":95,"line":1630},17,[93,1632,1553],{"class":228},[93,1634,1635],{"class":241},"/path/to/media:/media\n",[93,1637,1639,1641],{"class":95,"line":1638},18,[93,1640,1553],{"class":228},[93,1642,1643],{"class":241},"plex-transcode:/transcode\n",[93,1645,1647,1650],{"class":95,"line":1646},19,[93,1648,1649],{"class":1507},"  environment",[93,1651,1511],{"class":228},[93,1653,1655,1657],{"class":95,"line":1654},20,[93,1656,1553],{"class":228},[93,1658,1659],{"class":241},"TZ=Europe/Bucharest\n",[93,1661,1663,1665],{"class":95,"line":1662},21,[93,1664,1553],{"class":228},[93,1666,1667],{"class":241},"PUID=0\n",[93,1669,1671,1673],{"class":95,"line":1670},22,[93,1672,1553],{"class":228},[93,1674,1675],{"class":241},"PGID=0\n",[93,1677,1679,1681],{"class":95,"line":1678},23,[93,1680,1553],{"class":228},[93,1682,1683],{"class":241},"VERSION=docker\n",[93,1685,1687,1689],{"class":95,"line":1686},24,[93,1688,1553],{"class":228},[93,1690,1691],{"class":241},"NVIDIA_VISIBLE_DEVICES=all\n",[93,1693,1695,1697],{"class":95,"line":1694},25,[93,1696,1553],{"class":228},[93,1698,1699],{"class":241},"NVIDIA_DRIVER_CAPABILITIES=video,compute,utility\n",[93,1701,1703,1706],{"class":95,"line":1702},26,[93,1704,1705],{"class":1507},"  deploy",[93,1707,1511],{"class":228},[93,1709,1711,1714],{"class":95,"line":1710},27,[93,1712,1713],{"class":1507},"    resources",[93,1715,1511],{"class":228},[93,1717,1719,1722],{"class":95,"line":1718},28,[93,1720,1721],{"class":1507},"      reservations",[93,1723,1511],{"class":228},[93,1725,1727,1730],{"class":95,"line":1726},29,[93,1728,1729],{"class":1507},"        devices",[93,1731,1511],{"class":228},[93,1733,1735,1738,1741,1743],{"class":95,"line":1734},30,[93,1736,1737],{"class":228},"          - ",[93,1739,1740],{"class":1507},"driver",[93,1742,253],{"class":228},[93,1744,1745],{"class":241},"nvidia\n",[93,1747,1749,1752,1754],{"class":95,"line":1748},31,[93,1750,1751],{"class":1507},"            count",[93,1753,253],{"class":228},[93,1755,1756],{"class":241},"all\n",[93,1758,1760,1763,1765,1768,1771,1774,1776,1779],{"class":95,"line":1759},32,[93,1761,1762],{"class":1507},"            capabilities",[93,1764,238],{"class":228},[93,1766,1767],{"class":241},"video",[93,1769,1770],{"class":228},",",[93,1772,1773],{"class":241},"compute",[93,1775,1770],{"class":228},[93,1777,1778],{"class":241},"utility",[93,1780,1781],{"class":228},"]\n",[10,1783,1784],{},"The ports section is the standard Plex setup - nothing special there. The volumes are where I mount my media and config. But the real magic happens in those environment variables and the deploy section.",[1264,1786,1788],{"id":1787},"why-this-works-the-technical-bit","Why this works (the technical bit)",[10,1790,1791,1792,1794],{},"This ",[50,1793,1248],{}," section is what finally made everything click:",[84,1796,1798],{"className":1498,"code":1797,"language":1500,"meta":89,"style":89},"deploy:\n  resources:\n    reservations:\n      devices:\n        - driver: nvidia\n          count: all\n          capabilities: [video,compute,utility]\n",[50,1799,1800,1807,1814,1821,1828,1839,1848],{"__ignoreMap":89},[93,1801,1802,1805],{"class":95,"line":96},[93,1803,1804],{"class":1507},"deploy",[93,1806,1511],{"class":228},[93,1808,1809,1812],{"class":95,"line":102},[93,1810,1811],{"class":1507},"  resources",[93,1813,1511],{"class":228},[93,1815,1816,1819],{"class":95,"line":108},[93,1817,1818],{"class":1507},"    reservations",[93,1820,1511],{"class":228},[93,1822,1823,1826],{"class":95,"line":114},[93,1824,1825],{"class":1507},"      devices",[93,1827,1511],{"class":228},[93,1829,1830,1833,1835,1837],{"class":95,"line":121},[93,1831,1832],{"class":228},"        - ",[93,1834,1740],{"class":1507},[93,1836,253],{"class":228},[93,1838,1745],{"class":241},[93,1840,1841,1844,1846],{"class":95,"line":127},[93,1842,1843],{"class":1507},"          count",[93,1845,253],{"class":228},[93,1847,1756],{"class":241},[93,1849,1850,1853,1855,1857,1859,1861,1863,1865],{"class":95,"line":133},[93,1851,1852],{"class":1507},"          capabilities",[93,1854,238],{"class":228},[93,1856,1767],{"class":241},[93,1858,1770],{"class":228},[93,1860,1773],{"class":241},[93,1862,1770],{"class":228},[93,1864,1778],{"class":241},[93,1866,1781],{"class":228},[10,1868,1869],{},"When Docker sees this, it actually uses the Nvidia Container Runtime instead of the default one. This runtime does three critical things:",[17,1871,1872,1875,1880],{},[20,1873,1874],{},"Mounts all the Nvidia userspace libraries into the container",[20,1876,1877,1878],{},"Exposes the GPU devices at ",[50,1879,1168],{},[20,1881,1882],{},"Sets up the proper permissions for video encoding/decoding",[10,1884,1885,1888,1889,1892,1893,1896],{},[45,1886,1887],{},"Important gotcha",": Don't use ",[50,1890,1891],{},"capabilities: [gpu]"," like some guides suggest. I spent an hour debugging why that didn't work - turns out you need ",[50,1894,1895],{},"[video,compute,utility]"," for transcoding workloads specifically.",[30,1898],{},[33,1900,1902],{"id":1901},"double-checking-the-gpu-works","Double-checking the GPU works",[10,1904,1905],{},"Once your container is up, run these commands to make sure everything's actually working:",[84,1907,1909],{"className":1273,"code":1908,"language":1275,"meta":89,"style":89},"# Are the Nvidia devices there?\ndocker exec plex ls -la /dev/nvidia*\n\n# This is the critical one - are the libraries mounted?\ndocker exec plex bash -c \"ldconfig -p | grep -i nvidia\"\n\n# Can we talk to the driver?\ndocker exec plex cat /proc/driver/nvidia/version\n",[50,1910,1911,1916,1938,1942,1947,1964,1968,1973],{"__ignoreMap":89},[93,1912,1913],{"class":95,"line":96},[93,1914,1915],{"class":1282},"# Are the Nvidia devices there?\n",[93,1917,1918,1920,1923,1926,1929,1932,1935],{"class":95,"line":102},[93,1919,1440],{"class":1288},[93,1921,1922],{"class":241}," exec",[93,1924,1925],{"class":241}," plex",[93,1927,1928],{"class":241}," ls",[93,1930,1931],{"class":234}," -la",[93,1933,1934],{"class":241}," /dev/nvidia",[93,1936,1937],{"class":234},"*\n",[93,1939,1940],{"class":95,"line":108},[93,1941,118],{"emptyLinePlaceholder":117},[93,1943,1944],{"class":95,"line":114},[93,1945,1946],{"class":1282},"# This is the critical one - are the libraries mounted?\n",[93,1948,1949,1951,1953,1955,1958,1961],{"class":95,"line":121},[93,1950,1440],{"class":1288},[93,1952,1922],{"class":241},[93,1954,1925],{"class":241},[93,1956,1957],{"class":241}," bash",[93,1959,1960],{"class":234}," -c",[93,1962,1963],{"class":241}," \"ldconfig -p | grep -i nvidia\"\n",[93,1965,1966],{"class":95,"line":127},[93,1967,118],{"emptyLinePlaceholder":117},[93,1969,1970],{"class":95,"line":133},[93,1971,1972],{"class":1282},"# Can we talk to the driver?\n",[93,1974,1975,1977,1979,1981,1984],{"class":95,"line":138},[93,1976,1440],{"class":1288},[93,1978,1922],{"class":241},[93,1980,1925],{"class":241},[93,1982,1983],{"class":241}," cat",[93,1985,1986],{"class":241}," /proc/driver/nvidia/version\n",[10,1988,1989,1990,1993,1994,1997],{},"The second command is the one that matters most. If ",[50,1991,1992],{},"ldconfig -p | grep nvidia"," comes back empty, the Nvidia Container Runtime isn't being used properly. Go back and check your ",[50,1995,1996],{},"daemon.json"," setup.",[30,1999],{},[33,2001,2003],{"id":2002},"making-plex-use-the-gpu","Making Plex use the GPU",[10,2005,2006],{},"The Plex side is pretty straightforward:",[378,2008,2009,2015,2021,2027,2032],{},[20,2010,2011,2012],{},"Open the web UI at ",[50,2013,2014],{},"http://your-server:32400/web",[20,2016,2017,2018],{},"Hit ",[45,2019,2020],{},"Settings > Transcoder",[20,2022,2023,2024],{},"Check ",[45,2025,2026],{},"\"Use hardware acceleration when available\"",[20,2028,2023,2029],{},[45,2030,2031],{},"\"Use hardware-accelerated video encoding\"",[20,2033,2034],{},"Save it",[10,2036,2037],{},"Nothing fancy here. Plex should automatically detect the GPU once the container has proper access.",[30,2039],{},[33,2041,2043],{"id":2042},"testing-if-its-actually-working","Testing if it's actually working",[10,2045,2046],{},"Now for the moment of truth. Start playing something that needs transcoding - maybe force a lower quality stream on your phone or tablet - and run:",[84,2048,2050],{"className":1273,"code":2049,"language":1275,"meta":89,"style":89},"watch -n 1 nvidia-smi\n",[50,2051,2052],{"__ignoreMap":89},[93,2053,2054,2057,2060,2063],{"class":95,"line":96},[93,2055,2056],{"class":1288},"watch",[93,2058,2059],{"class":234}," -n",[93,2061,2062],{"class":234}," 1",[93,2064,2065],{"class":241}," nvidia-smi\n",[10,2067,2068],{},"Here's what you want to see:",[17,2070,2071,2086,2089],{},[20,2072,2073,2074,2077,2078,2081,2082,2085],{},"GPU power state drops from ",[45,2075,2076],{},"P8"," (basically sleeping) down to ",[45,2079,2080],{},"P1"," or ",[45,2083,2084],{},"P2"," (actually working)",[20,2087,2088],{},"Power draw jumps from the idle ~14W up to 30-50W range",[20,2090,2091],{},"Encoder and decoder utilization show something other than zero",[10,2093,2094,2095,2098],{},"In Plex's dashboard, any active transcoding sessions will have ",[45,2096,2097],{},"(hw)"," next to them. That's your confirmation that it's using the GPU instead of grinding away on the CPU.",[10,2100,2101],{},"The first time I saw that \"(hw)\" tag, I actually did a little victory dance. After all those evenings of frustration and missing my simple Intel setup, it was finally working better than I'd imagined.",[30,2103],{},[33,2105,2107],{"id":2106},"when-things-go-sideways","When things go sideways",[1264,2109,2111],{"id":2110},"gpu-shows-up-but-no-libraries","GPU shows up but no libraries",[10,2113,2114,253,2117,2119,2120,2122],{},[45,2115,2116],{},"What you see",[50,2118,1168],{}," devices exist, but ",[50,2121,1992],{}," returns nothing.",[10,2124,2125,2128,2129,2131],{},[45,2126,2127],{},"What's wrong",": Docker isn't using the Nvidia Container Runtime. Check that you have the ",[50,2130,1248],{}," section in your compose file, not just environment variables.",[1264,2133,2135],{"id":2134},"could-not-select-device-driver-nvidia","\"could not select device driver nvidia\"",[10,2137,2138],{},"This error message is basically Docker's way of saying \"I don't understand what you want.\"",[10,2140,2141,2144,2145,386,2147,2150,2151,2154],{},[45,2142,2143],{},"Fix",": Change ",[50,2146,1891],{},[50,2148,2149],{},"capabilities: [video,compute,utility]",". The generic ",[50,2152,2153],{},"gpu"," capability doesn't work for transcoding.",[1264,2156,2158],{"id":2157},"gpu-works-but-plex-ignores-it","GPU works but Plex ignores it",[10,2160,2161,2162,2164],{},"Most frustrating scenario. Your ",[50,2163,1175],{}," shows the GPU, all the libraries are there, but Plex keeps hammering the CPU anyway.",[10,2166,2167],{},"Check these things:",[378,2169,2170,2173,2176],{},[20,2171,2172],{},"Hardware transcoding is actually enabled in Plex settings (sounds obvious, but I forgot this once)",[20,2174,2175],{},"The codec you're trying to transcode actually supports hardware acceleration",[20,2177,2178,2179,2182],{},"Plex logs at ",[50,2180,2181],{},"/config/Library/Application Support/Plex Media Server/Logs/"," - they'll tell you exactly why it's falling back to software transcoding",[30,2184],{},[33,2186,2188],{"id":2187},"what-not-to-do-lessons-from-my-failures","What not to do (lessons from my failures)",[17,2190,2191,2199,2208],{},[20,2192,2193,2198],{},[45,2194,2195,2196],{},"Skip ",[50,2197,1164],{}," - It's a security nightmare and doesn't even solve the library mounting problem",[20,2200,2201,2204,2205,2207],{},[45,2202,2203],{},"Don't manually mount GPU devices"," - Use the proper runtime instead of hardcoding ",[50,2206,1168],{}," paths",[20,2209,2210,2216,2217,2219],{},[45,2211,2212,2213],{},"Avoid ",[50,2214,2215],{},"runtime: nvidia"," - Works but is legacy. The new ",[50,2218,1248],{}," approach is cleaner",[10,2221,2222],{},"I tried all of these before finding the right way. Save yourself the time.",[30,2224],{},[10,2226,2227],{},"That's it. No more CPU transcoding, no more overheated servers, no more three-minute waits for a 30-second preview to load. My new setup finally lives up to the hardware upgrade.",[10,2229,2230],{},"The GPU transcoding setup looks complicated when you're staring at failed configs at 11 PM, missing the simplicity of your old Intel setup. But once you understand what Docker actually needs to make the Nvidia runtime work, it's pretty straightforward.",[10,2232,2233],{},"Now go enjoy your hardware-accelerated Plex setup. Trust me, after dealing with software transcoding for years, you've more than earned this upgrade.",[30,2235],{},[33,2237,2239],{"id":2238},"related-homelab-articles","Related Homelab Articles",[10,2241,2242],{},"If you found this useful, you might want to check out some of my other Docker and self-hosting guides:",[17,2244,2245,2254,2262],{},[20,2246,2247,2253],{},[45,2248,2249],{},[1142,2250,2252],{"href":2251},"/homelab/cloudflare-r2-backup-coolify-homelab","Cloudflare R2 Backup for Coolify Homelab"," - Don't forget to backup your media library and container configurations",[20,2255,2256,2261],{},[45,2257,2258],{},[1142,2259,2260],{"href":1144},"Umami + Portainer + Traefik Setup"," - Complete container orchestration and monitoring setup",[20,2263,2264,2270],{},[45,2265,2266],{},[1142,2267,2269],{"href":2268},"/homelab/coolify-reverse-proxy-to-another-host","Coolify Reverse Proxy to Another Host"," - Advanced networking for multi-host deployments",[30,2272],{},[455,2274,2275],{},"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 .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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":89,"searchDepth":102,"depth":102,"links":2277},[2278,2279,2280,2285,2288,2289,2290,2291,2296,2297],{"id":1186,"depth":102,"text":1187},{"id":1228,"depth":102,"text":1229},{"id":1261,"depth":102,"text":1262,"children":2281},[2282,2283,2284],{"id":1266,"depth":108,"text":1267},{"id":1334,"depth":108,"text":1335},{"id":1421,"depth":108,"text":1422},{"id":1486,"depth":102,"text":1487,"children":2286},[2287],{"id":1787,"depth":108,"text":1788},{"id":1901,"depth":102,"text":1902},{"id":2002,"depth":102,"text":2003},{"id":2042,"depth":102,"text":2043},{"id":2106,"depth":102,"text":2107,"children":2292},[2293,2294,2295],{"id":2110,"depth":108,"text":2111},{"id":2134,"depth":108,"text":2135},{"id":2157,"depth":108,"text":2158},{"id":2187,"depth":102,"text":2188},{"id":2238,"depth":102,"text":2239},"2026-01-29","A practical guide to enabling Nvidia GPU hardware transcoding in Plex Media Server running in Docker",{"totalTime":2301,"tool":2302,"steps":2306},"PT30M",[2303,2304,2305],"Linux system with Nvidia GPU","Docker and Docker Compose","Plex Pass subscription",[2307,2310,2313,2316,2319,2322,2325,2328],{"name":2308,"text":2309},"Install Nvidia drivers and container toolkit","Install nvidia, nvidia-utils, and nvidia-container-toolkit packages using your distribution's package manager.",{"name":2311,"text":2312},"Configure Docker for Nvidia runtime","Create or edit /etc/docker/daemon.json to add the nvidia runtime configuration, then restart the Docker service.",{"name":2314,"text":2315},"Verify installation","Run docker info | grep runtime to confirm Nvidia runtime is available, and nvidia-smi to verify GPU is detected.",{"name":2317,"text":2318},"Configure Docker Compose for Plex","Add the deploy.resources.reservations section with nvidia driver and video,compute,utility capabilities to your Plex container configuration.",{"name":2320,"text":2321},"Start Plex container","Run docker compose up -d to start Plex with GPU passthrough enabled.",{"name":2323,"text":2324},"Verify GPU access in container","Run docker exec plex ldconfig -p | grep nvidia to confirm Nvidia libraries are properly mounted inside the container.",{"name":2326,"text":2327},"Enable hardware transcoding in Plex","Open Plex web UI, go to Settings > Transcoder, and enable Use hardware acceleration when available and Use hardware-accelerated video encoding.",{"name":2329,"text":2330},"Test GPU transcoding","Play media that requires transcoding and run nvidia-smi to verify GPU utilization increases and Plex shows (hw) tag on active streams.",{"lastmod":2298,"external":468},"/blog/plex-gpu-transcoding-nvidia-docker",{"title":1135,"description":2299},"blog/16.plex-gpu-transcoding-nvidia-docker",[1508,2336,1440,2337,2153,2338],"nvidia","transcoding","archlinux","f-jo1vef0V5hoVrRTgyU7sRO2cZg_NMPhZht3FE2I7k",{"id":2341,"title":2342,"body":2343,"category":2893,"date":2894,"description":2895,"draft":468,"extension":469,"howto":490,"image":490,"meta":2896,"navigation":117,"path":2897,"seo":2898,"stem":2899,"tags":2900,"__hash__":2904},"blog/blog/15.vite-s-proxy-overlooked-feature.md","Vite's Proxy: The Development Feature You're Probably Overlooking",{"type":7,"value":2344,"toc":2882},[2345,2349,2352,2356,2359,2365,2368,2371,2382,2386,2389,2395,2510,2514,2517,2537,2541,2544,2611,2618,2622,2631,2820,2824,2827,2841,2845,2848,2852,2856,2859,2879],[2346,2347,2342],"h1",{"id":2348},"vites-proxy-the-development-feature-youre-probably-overlooking",[10,2350,2351],{},"Let's talk about a practical Vite feature that doesn't get enough attention: the proxy configuration. If you're building SPAs that communicate with backend servers, this might save you some serious headaches.",[33,2353,2355],{"id":2354},"the-development-vs-production-disconnect","The Development vs Production Disconnect",[10,2357,2358],{},"When you're building a modern web app, your production setup typically looks something like this:",[84,2360,2363],{"className":2361,"code":2362,"language":211},[209],"User → Nginx → Frontend SPA\n       ↓\n       Backend API\n",[50,2364,2362],{"__ignoreMap":89},[10,2366,2367],{},"Nginx (or similar) handles routing API requests to your backend while serving your frontend assets. But during development, you're running your frontend on one port and your backend on another.",[10,2369,2370],{},"This disconnect creates several issues:",[17,2372,2373,2376,2379],{},[20,2374,2375],{},"CORS errors",[20,2377,2378],{},"Environment-specific code paths",[20,2380,2381],{},"Inconsistencies between development and production",[33,2383,2385],{"id":2384},"how-vites-proxy-solves-this","How Vite's Proxy Solves This",[10,2387,2388],{},"Vite's proxy configuration acts as a development-time stand-in for your production server setup. It intercepts specified requests from your frontend and forwards them to your backend server.",[10,2390,2391,2392,82],{},"Here's a basic setup in your ",[50,2393,2394],{},"vite.config.js",[84,2396,2400],{"className":2397,"code":2398,"language":2399,"meta":89,"style":89},"language-js shiki shiki-themes github-light github-dark","import { defineConfig } from 'vite'\nimport react from '@vitejs/plugin-react'\n\nexport default defineConfig({\n  plugins: [react()],\n  server: {\n    proxy: {\n      '/api': {\n        target: 'http://localhost:3000',\n        changeOrigin: true\n      }\n    }\n  }\n})\n","js",[50,2401,2402,2416,2428,2432,2446,2457,2462,2467,2474,2484,2491,2496,2500,2505],{"__ignoreMap":89},[93,2403,2404,2407,2410,2413],{"class":95,"line":96},[93,2405,2406],{"class":1446},"import",[93,2408,2409],{"class":228}," { defineConfig } ",[93,2411,2412],{"class":1446},"from",[93,2414,2415],{"class":241}," 'vite'\n",[93,2417,2418,2420,2423,2425],{"class":95,"line":102},[93,2419,2406],{"class":1446},[93,2421,2422],{"class":228}," react ",[93,2424,2412],{"class":1446},[93,2426,2427],{"class":241}," '@vitejs/plugin-react'\n",[93,2429,2430],{"class":95,"line":108},[93,2431,118],{"emptyLinePlaceholder":117},[93,2433,2434,2437,2440,2443],{"class":95,"line":114},[93,2435,2436],{"class":1446},"export",[93,2438,2439],{"class":1446}," default",[93,2441,2442],{"class":1288}," defineConfig",[93,2444,2445],{"class":228},"({\n",[93,2447,2448,2451,2454],{"class":95,"line":121},[93,2449,2450],{"class":228},"  plugins: [",[93,2452,2453],{"class":1288},"react",[93,2455,2456],{"class":228},"()],\n",[93,2458,2459],{"class":95,"line":127},[93,2460,2461],{"class":228},"  server: {\n",[93,2463,2464],{"class":95,"line":133},[93,2465,2466],{"class":228},"    proxy: {\n",[93,2468,2469,2472],{"class":95,"line":138},[93,2470,2471],{"class":241},"      '/api'",[93,2473,1358],{"class":228},[93,2475,2476,2479,2482],{"class":95,"line":143},[93,2477,2478],{"class":228},"        target: ",[93,2480,2481],{"class":241},"'http://localhost:3000'",[93,2483,259],{"class":228},[93,2485,2486,2489],{"class":95,"line":149},[93,2487,2488],{"class":228},"        changeOrigin: ",[93,2490,281],{"class":234},[93,2492,2493],{"class":95,"line":154},[93,2494,2495],{"class":228},"      }\n",[93,2497,2498],{"class":95,"line":159},[93,2499,1393],{"class":228},[93,2501,2502],{"class":95,"line":164},[93,2503,2504],{"class":228},"  }\n",[93,2506,2507],{"class":95,"line":170},[93,2508,2509],{"class":228},"})\n",[33,2511,2513],{"id":2512},"practical-benefits","Practical Benefits",[10,2515,2516],{},"This simple configuration provides several immediate advantages:",[378,2518,2519,2525,2531],{},[20,2520,2521,2524],{},[45,2522,2523],{},"Consistent code"," - Use the same API paths in development and production",[20,2526,2527,2530],{},[45,2528,2529],{},"No CORS configuration"," - Requests appear to come from the same origin",[20,2532,2533,2536],{},[45,2534,2535],{},"Realistic testing"," - Your local environment behaves more like production",[33,2538,2540],{"id":2539},"real-world-application","Real-World Application",[10,2542,2543],{},"Consider a typical API request in your frontend code:",[84,2545,2547],{"className":2397,"code":2546,"language":2399,"meta":89,"style":89},"// This works identically in both environments\nasync function getUsers() {\n  const response = await fetch('/api/users');\n  return response.json();\n}\n",[50,2548,2549,2554,2568,2594,2607],{"__ignoreMap":89},[93,2550,2551],{"class":95,"line":96},[93,2552,2553],{"class":1282},"// This works identically in both environments\n",[93,2555,2556,2559,2562,2565],{"class":95,"line":102},[93,2557,2558],{"class":1446},"async",[93,2560,2561],{"class":1446}," function",[93,2563,2564],{"class":1288}," getUsers",[93,2566,2567],{"class":228},"() {\n",[93,2569,2570,2573,2576,2579,2582,2585,2588,2591],{"class":95,"line":108},[93,2571,2572],{"class":1446},"  const",[93,2574,2575],{"class":234}," response",[93,2577,2578],{"class":1446}," =",[93,2580,2581],{"class":1446}," await",[93,2583,2584],{"class":1288}," fetch",[93,2586,2587],{"class":228},"(",[93,2589,2590],{"class":241},"'/api/users'",[93,2592,2593],{"class":228},");\n",[93,2595,2596,2599,2602,2604],{"class":95,"line":114},[93,2597,2598],{"class":1446},"  return",[93,2600,2601],{"class":228}," response.",[93,2603,221],{"class":1288},[93,2605,2606],{"class":228},"();\n",[93,2608,2609],{"class":95,"line":121},[93,2610,111],{"class":228},[10,2612,2613,2614,2617],{},"During development, Vite intercepts the ",[50,2615,2616],{},"/api/users"," request and forwards it to your backend. In production, your web server does the same thing. Your frontend code remains unchanged between environments.",[33,2619,2621],{"id":2620},"beyond-basic-configuration","Beyond Basic Configuration",[10,2623,2624,2625,2630],{},"The proxy is powered by ",[1142,2626,2629],{"href":2627,"rel":2628},"https://github.com/http-party/node-http-proxy",[1329],"http-proxy",", giving you additional options when needed:",[84,2632,2634],{"className":2397,"code":2633,"language":2399,"meta":89,"style":89},"server: {\n  proxy: {\n    '/api': {\n      target: 'http://localhost:3000',\n      changeOrigin: true,\n      rewrite: (path) => path.replace(/^\\/api/, ''),\n      configure: (proxy, options) => {\n        // Add authentication headers for development\n        proxy.on('proxyReq', (proxyReq) => {\n          proxyReq.setHeader('Authorization', 'Bearer dev-token');\n        });\n      }\n    }\n  }\n}\n",[50,2635,2636,2643,2650,2657,2666,2676,2727,2749,2754,2779,2799,2804,2808,2812,2816],{"__ignoreMap":89},[93,2637,2638,2641],{"class":95,"line":96},[93,2639,2640],{"class":1288},"server",[93,2642,1358],{"class":228},[93,2644,2645,2648],{"class":95,"line":102},[93,2646,2647],{"class":1288},"  proxy",[93,2649,1358],{"class":228},[93,2651,2652,2655],{"class":95,"line":108},[93,2653,2654],{"class":241},"    '/api'",[93,2656,1358],{"class":228},[93,2658,2659,2662,2664],{"class":95,"line":114},[93,2660,2661],{"class":228},"      target: ",[93,2663,2481],{"class":241},[93,2665,259],{"class":228},[93,2667,2668,2671,2674],{"class":95,"line":121},[93,2669,2670],{"class":228},"      changeOrigin: ",[93,2672,2673],{"class":234},"true",[93,2675,259],{"class":228},[93,2677,2678,2681,2684,2688,2691,2694,2697,2700,2702,2705,2708,2712,2716,2718,2721,2724],{"class":95,"line":127},[93,2679,2680],{"class":1288},"      rewrite",[93,2682,2683],{"class":228},": (",[93,2685,2687],{"class":2686},"s4XuR","path",[93,2689,2690],{"class":228},") ",[93,2692,2693],{"class":1446},"=>",[93,2695,2696],{"class":228}," path.",[93,2698,2699],{"class":1288},"replace",[93,2701,2587],{"class":228},[93,2703,2704],{"class":241},"/",[93,2706,2707],{"class":1446},"^",[93,2709,2711],{"class":2710},"snhLl","\\/",[93,2713,2715],{"class":2714},"sA_wV","api",[93,2717,2704],{"class":241},[93,2719,2720],{"class":228},", ",[93,2722,2723],{"class":241},"''",[93,2725,2726],{"class":228},"),\n",[93,2728,2729,2732,2734,2737,2739,2742,2744,2746],{"class":95,"line":133},[93,2730,2731],{"class":1288},"      configure",[93,2733,2683],{"class":228},[93,2735,2736],{"class":2686},"proxy",[93,2738,2720],{"class":228},[93,2740,2741],{"class":2686},"options",[93,2743,2690],{"class":228},[93,2745,2693],{"class":1446},[93,2747,2748],{"class":228}," {\n",[93,2750,2751],{"class":95,"line":138},[93,2752,2753],{"class":1282},"        // Add authentication headers for development\n",[93,2755,2756,2759,2762,2764,2767,2770,2773,2775,2777],{"class":95,"line":143},[93,2757,2758],{"class":228},"        proxy.",[93,2760,2761],{"class":1288},"on",[93,2763,2587],{"class":228},[93,2765,2766],{"class":241},"'proxyReq'",[93,2768,2769],{"class":228},", (",[93,2771,2772],{"class":2686},"proxyReq",[93,2774,2690],{"class":228},[93,2776,2693],{"class":1446},[93,2778,2748],{"class":228},[93,2780,2781,2784,2787,2789,2792,2794,2797],{"class":95,"line":149},[93,2782,2783],{"class":228},"          proxyReq.",[93,2785,2786],{"class":1288},"setHeader",[93,2788,2587],{"class":228},[93,2790,2791],{"class":241},"'Authorization'",[93,2793,2720],{"class":228},[93,2795,2796],{"class":241},"'Bearer dev-token'",[93,2798,2593],{"class":228},[93,2800,2801],{"class":95,"line":154},[93,2802,2803],{"class":228},"        });\n",[93,2805,2806],{"class":95,"line":159},[93,2807,2495],{"class":228},[93,2809,2810],{"class":95,"line":164},[93,2811,1393],{"class":228},[93,2813,2814],{"class":95,"line":170},[93,2815,2504],{"class":228},[93,2817,2818],{"class":95,"line":176},[93,2819,111],{"class":228},[33,2821,2823],{"id":2822},"when-to-use-this","When To Use This",[10,2825,2826],{},"This approach is particularly valuable when:",[17,2828,2829,2832,2835,2838],{},[20,2830,2831],{},"Your app makes API calls to the same domain in production",[20,2833,2834],{},"You're using relative URLs for API endpoints",[20,2836,2837],{},"You want to minimize environment-specific code",[20,2839,2840],{},"You need to test with realistic API interactions",[33,2842,2844],{"id":2843},"the-takeaway","The Takeaway",[10,2846,2847],{},"Vite's proxy configuration is a straightforward solution to a common development problem. It bridges the gap between development and production environments with minimal setup, letting you focus on building features rather than managing environment differences.",[33,2849,2851],{"id":2850},"next-time-you-start-a-vite-project-that-needs-to-communicate-with-a-backend-consider-setting-up-the-proxy-configuration-early-its-a-small-investment-that-pays-off-throughout-the-development-process","Next time you start a Vite project that needs to communicate with a backend, consider setting up the proxy configuration early. It's a small investment that pays off throughout the development process.",[33,2853,2855],{"id":2854},"related-development-articles","Related Development Articles",[10,2857,2858],{},"For more Vue 3 and modern development tooling guides:",[17,2860,2861,2870],{},[20,2862,2863,2869],{},[45,2864,2865],{},[1142,2866,2868],{"href":2867},"/vue/firebase-web-notifications-tutorial-2023","Firebase Web Notifications Setup"," - Real-world Vite configuration with production builds and environment variables",[20,2871,2872,2878],{},[45,2873,2874],{},[1142,2875,2877],{"href":2876},"/vue/pinia-setup-stores","Pinia Setup Stores Guide"," - Modern state management patterns for Vue 3 applications",[455,2880,2881],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}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 .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .snhLl, html code.shiki .snhLl{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold}html pre.shiki code .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}",{"title":89,"searchDepth":102,"depth":102,"links":2883},[2884,2885,2886,2887,2888,2889,2890,2891,2892],{"id":2354,"depth":102,"text":2355},{"id":2384,"depth":102,"text":2385},{"id":2512,"depth":102,"text":2513},{"id":2539,"depth":102,"text":2540},{"id":2620,"depth":102,"text":2621},{"id":2822,"depth":102,"text":2823},{"id":2843,"depth":102,"text":2844},{"id":2850,"depth":102,"text":2851},{"id":2854,"depth":102,"text":2855},"development","2025-05-24","Learn how Vite's proxy configuration can bridge the gap between development and production environments, eliminating CORS issues and simplifying API interactions.",{"lastmod":2298,"external":468},"/blog/vite-s-proxy-overlooked-feature",{"title":2342,"description":2895},"blog/15.vite-s-proxy-overlooked-feature",[2901,2902,2893,2736,2903],"vite","frontend","spa","7AkZqNiQSbcXH3G-6CQ41R7pvn42P7xuyVl2rMyM0oM",{"id":2906,"title":2907,"body":2908,"category":501,"date":3175,"description":3176,"draft":468,"extension":469,"howto":490,"image":490,"meta":3177,"navigation":117,"path":3178,"seo":3179,"stem":3180,"tags":3181,"__hash__":3185},"blog/blog/14.cloudflare-r2-backup-coolify-homelab.md","Cloudflare R2 – A Smart Backup Solution for Coolify-Based Homelab Setups",{"type":7,"value":2909,"toc":3165},[2910,2926,2933,2935,2939,2949,2956,2958,2962,2968,3006,3013,3015,3019,3026,3063,3070,3072,3076,3086,3103,3106,3108,3112,3119,3126,3134,3136,3139],[10,2911,2912,2913,2916,2917,2921,2922,2925],{},"If you’re running a self-hosted platform like ",[45,2914,2915],{},"Coolify"," in your homelab, you’ve probably asked yourself: ",[2918,2919,2920],"em",{},"“How do I back up my databases and app data off-site without breaking the bank?”"," Enter ",[45,2923,2924],{},"Cloudflare R2"," — an S3-compatible object storage service with no egress fees and a price point that makes it a great fit for DIY DevOps setups.",[10,2927,2928,2929,2932],{},"In this post, I’ll show you why ",[45,2930,2931],{},"Cloudflare R2 is a solid off-site backup solution for Coolify deployments",", how to set it up, and how to get the most out of its generous free tier.",[30,2934],{},[33,2936,2938],{"id":2937},"why-off-site-backups-matter-in-homelabs","Why Off-Site Backups Matter in Homelabs",[10,2940,2941,2942,2720,2945,2948],{},"If you’re self-hosting apps via Coolify, chances are you’re also managing your own ",[45,2943,2944],{},"PostgreSQL",[45,2946,2947],{},"MongoDB",", or other data services. While Coolify handles app orchestration and even provides scheduled database backups, those backups often live on the same physical machine — not ideal if the disk fails or something goes wrong.",[10,2950,2951,2952,2955],{},"A proper ",[45,2953,2954],{},"backup strategy"," includes at least one off-site backup, and that’s where R2 steps in.",[30,2957],{},[33,2959,2961],{"id":2960},"why-cloudflare-r2","Why Cloudflare R2?",[10,2963,2964,2965,2967],{},"Here’s what makes ",[45,2966,2924],{}," a compelling choice:",[17,2969,2970,2976,2982,2988,2994,3000],{},[20,2971,2972,2975],{},[45,2973,2974],{},"S3-compatible",": Works out-of-the-box with Coolify's backup feature",[20,2977,2978,2981],{},[45,2979,2980],{},"No egress fees",": Restore your backups without paying for data retrieval",[20,2983,2984,2987],{},[45,2985,2986],{},"Affordable storage",": ~$0.015/GB/month",[20,2989,2990,2993],{},[45,2991,2992],{},"Generous free tier",": 10 GB storage, 1 million write ops, and 10 million read ops per month — more than enough for typical homelab setups",[20,2995,2996,2999],{},[45,2997,2998],{},"Simple API & integration",": Setup is fast and painless",[20,3001,3002,3005],{},[45,3003,3004],{},"Works great even at small scale",": Perfect for self-hosters and hobbyists",[10,3007,3008,3009,3012],{},"For homelab users, that ",[45,3010,3011],{},"10 GB free tier"," can go a long way — especially if you're only storing daily database dumps or compressed archives of config and essential data. I'm using it for two sepparate coolify instance with plenty of databases deployed and at the moment i'm still under 500MB usage.",[30,3014],{},[33,3016,3018],{"id":3017},"setting-up-r2-with-coolify","Setting Up R2 with Coolify",[10,3020,3021,3022,3025],{},"Coolify already supports ",[45,3023,3024],{},"S3-compatible storage"," for backups. Here's a quick walkthrough:",[378,3027,3028,3034,3040,3053,3060],{},[20,3029,3030,3033],{},[45,3031,3032],{},"Create an R2 Bucket"," in your Cloudflare dashboard.",[20,3035,3036,3037,339],{},"Generate ",[45,3038,3039],{},"Access Key + Secret Key",[20,3041,3042,3043,3046,3047,3050],{},"In ",[45,3044,3045],{},"Coolify Admin Panel -> S3 Storage -> Add",", add your R2 credentials, chosen bucket and endpoint:",[3048,3049],"br",{},[50,3051,3052],{},"https://\u003Caccountid>.r2.cloudflarestorage.com",[20,3054,3055,3056,3059],{},"Go to ",[45,3057,3058],{},"Project → Backups",", and enable backups for your databases.",[20,3061,3062],{},"Choose the R2 storage from the dropdown and set a cron schedule.",[10,3064,3065,3066,3069],{},"Coolify will now automatically dump your databases (e.g., via ",[50,3067,3068],{},"pg_dump",") and upload the files to R2 — no extra scripting needed.",[30,3071],{},[33,3073,3075],{"id":3074},"what-about-application-data","What About Application Data?",[10,3077,3078,3079,2720,3082,3085],{},"Coolify’s built-in backups only handle databases (and internal Coolify config). But if you want to back up Docker volumes — e.g., for apps like ",[45,3080,3081],{},"Vaultwarden",[45,3083,3084],{},"Nextcloud",", etc. — you can deploy tools like:",[17,3087,3088,3093,3098],{},[20,3089,3090],{},[45,3091,3092],{},"Duplicati",[20,3094,3095],{},[45,3096,3097],{},"Restic",[20,3099,3100],{},[45,3101,3102],{},"Rclone",[10,3104,3105],{},"Deploy them via Coolify itself, mount the appropriate volumes, and push the archives to R2 on a schedule.",[30,3107],{},[33,3109,3111],{"id":3110},"final-thoughts","Final Thoughts",[10,3113,3114,3115,3118],{},"Coolify makes self-hosting feel like a breeze, and ",[45,3116,3117],{},"Cloudflare R2 fills the off-site backup gap perfectly",". It’s cheap, simple to integrate, and scales well with your needs — even if you're just running a couple of apps at home.",[10,3120,3121,3122,3125],{},"And best of all? ",[45,3123,3124],{},"You will likely stay within the free tier",", making R2 a no-brainer backup solution that costs nothing but brings peace of mind.",[33,3127,3129,3130,3133],{"id":3128},"if-youre-already-using-cloudflare-for-dns-or-tunnels-this-is-a-natural-extension-to-your-stack-set-it-up-once-and-rest-easy-knowing-your-data-lives-beyond-your-own-machine","If you’re already using ",[45,3131,3132],{},"Cloudflare for DNS or tunnels",", this is a natural extension to your stack. Set it up once, and rest easy knowing your data lives beyond your own machine.",[33,3135,2239],{"id":2238},[10,3137,3138],{},"If you're building out your Coolify-based homelab, these guides might be helpful:",[17,3140,3141,3148,3157],{},[20,3142,3143,3147],{},[45,3144,3145],{},[1142,3146,2269],{"href":2268}," - Setting up multi-host deployments and networking",[20,3149,3150,3156],{},[45,3151,3152],{},[1142,3153,3155],{"href":3154},"/homelab/plex-gpu-transcoding-nvidia-docker","Plex GPU Transcoding with Docker"," - Advanced Docker configurations for media services",[20,3158,3159,3164],{},[45,3160,3161],{},[1142,3162,3163],{"href":1144},"Umami Analytics with Portainer and Traefik"," - Alternative container orchestration and monitoring setup",{"title":89,"searchDepth":102,"depth":102,"links":3166},[3167,3168,3169,3170,3171,3172,3174],{"id":2937,"depth":102,"text":2938},{"id":2960,"depth":102,"text":2961},{"id":3017,"depth":102,"text":3018},{"id":3074,"depth":102,"text":3075},{"id":3110,"depth":102,"text":3111},{"id":3128,"depth":102,"text":3173},"If you’re already using Cloudflare for DNS or tunnels, this is a natural extension to your stack. Set it up once, and rest easy knowing your data lives beyond your own machine.",{"id":2238,"depth":102,"text":2239},"2025-03-22","A simple guide to using Cloudflare R2 for off-site backups in Coolify homelab setups. The generous free tier makes it an ideal choice for hobbyists, providing ample storage and operations for typical homelab needs",{"lastmod":2298,"external":468},"/blog/cloudflare-r2-backup-coolify-homelab",{"title":2907,"description":3176},"blog/14.cloudflare-r2-backup-coolify-homelab",[60,3182,3183,3184],"backup","s3","gemini","wkua59dP4fM-VmZFzC_EsdWPhzKqFmMIGn9A8Fi6Fm8",{"id":3187,"title":3188,"body":3189,"category":3317,"date":3318,"description":3319,"draft":468,"extension":3320,"howto":490,"image":490,"meta":3321,"navigation":117,"path":3322,"seo":3323,"stem":3324,"tags":3325,"__hash__":3329},"blog/blog/13.from-paper-to-json - gdg-bucharest - 2024.mdx","From Paper to JSON - GDG Bucharest",{"type":7,"value":3190,"toc":3312},[3191,3197,3213,3228,3231,3235,3238,3242,3245,3271,3275,3278,3281,3284,3293],[3192,3193],"img",{"src":3194,"className":3195},"https://tere-ro.b-cdn.net/tere.ro/gdg-article-2024/gdg_tere_intro.jpeg",[3196],"w-full",[446,3198,3199],{},[10,3200,3201,3202,3207,3208],{},"🧑🏻‍💻 ",[1142,3203,3206],{"href":3204,"rel":3205},"https://github.com/andreitere/gdg_expense_vertex_ai",[1329],"Code"," | 🖥️ ",[1142,3209,3212],{"href":3210,"rel":3211},"https://andreitere.github.io/gdg_expense_vertex_ai/",[1329],"Presentation",[10,3214,3215,3216,3221,3222,3227],{},"Earlier this month, I had the chance to present at the ",[1142,3217,3220],{"href":3218,"rel":3219},"https://gdg.community.dev/events/details/google-gdg-bucharest-presents-data-processing-using-gemini-models-through-vertex-ai/",[1329],"Google Developer Meetup in Bucharest",", alongside my longstanding friend and mentor ",[1142,3223,3226],{"href":3224,"rel":3225},"https://luciangruia.ro/blog/building-knowledge-graphs-from-unstructured-data-at-gdg-bucharest/",[1329],"Lucian Gruia",", where I introduced a workflow for digitizing and processing expense receipts using AI.",[10,3229,3230],{},"In the end the task deemed to be fairly straightforward and simple but the presentation mainly aimed highlighting how easy is to introduce AI powered workflows in our apps and in our day to day activities.",[33,3232,3234],{"id":3233},"the-problem","The Problem",[10,3236,3237],{},"Being part of a big friend group, closing trips or other group activities proved challenging when it came to cost splitting. We were always reliant on expense-splitting apps (e.g., SplitWise). Lately, I've taken on the challenge of building such an app and discovered just how complex, yet rewarding, it is to create an automated solution that not only splits expenses but also itemizes receipts, tracks debts, and handles multiple currencies. The process opened my eyes to how much we can leverage AI and modern backend systems to make group activities smoother and more enjoyable.",[33,3239,3241],{"id":3240},"the-solution-from-paper-to-json","The Solution: From Paper to JSON",[10,3243,3244],{},"My presentation demonstrated how to digitize these paper receipts and convert them into structured JSON, ready for backend processing. Here's how it works:",[378,3246,3247,3253,3259,3265],{},[20,3248,3249,3252],{},[45,3250,3251],{},"Scan Receipts",": Start with scanning the physical receipts.",[20,3254,3255,3258],{},[45,3256,3257],{},"LLM-Powered Itemization",": Use Large Language Models via Vertex AI to extract and itemize data from receipts.",[20,3260,3261,3264],{},[45,3262,3263],{},"Generate JSON",": Convert the extracted data into structured JSON.",[20,3266,3267,3270],{},[45,3268,3269],{},"Backend Integration",": Feed the JSON into backend systems for processing and validation.",[33,3272,3274],{"id":3273},"results","Results",[10,3276,3277],{},"This workflow significantly enhances the user experience by simplifying complex, time-consuming processes into quick and seamless tasks. For the everyday user, this means no more struggling with manual data entry or dealing with frequent mistakes that slow them down.",[10,3279,3280],{},"By automating tedious processes, the workflow empowers users to focus on decision-making and more meaningful actions rather than getting bogged down in repetitive tasks. The result is a streamlined experience that feels intuitive and supportive, elevating the overall satisfaction and ease of use for anyone interacting with the system.",[10,3282,3283],{},"Presenting this solution at the GDG Bucharest meetup was an incredible experience, and I'm excited to see how AI continues to transform data processing workflows.",[10,3285,3286,3287,3207,3290],{},"| 🧑🏻‍💻 ",[1142,3288,3206],{"href":3204,"rel":3289},[1329],[1142,3291,3212],{"href":3210,"rel":3292},[1329],[3294,3295,3299,3300,3299,3303,3299,3306,3299,3309],"div",{"className":3296},[3297,3298],"flex","flex-col","\n  ",[3192,3301],{"src":3302},"https://tere-ro.b-cdn.net/tere.ro/gdg-article-2024/gdg_tere_lucian.jpeg",[3192,3304],{"src":3305},"https://tere-ro.b-cdn.net/tere.ro/gdg-article-2024/gdg_tere_public_interaction.jpeg",[3192,3307],{"src":3308},"https://tere-ro.b-cdn.net/tere.ro/gdg-article-2024/gdg_tere_magic.jpeg",[3192,3310],{"src":3311},"https://tere-ro.b-cdn.net/tere.ro/gdg-article-2024/gdg_tere_gemini_models.jpeg",{"title":89,"searchDepth":102,"depth":102,"links":3313},[3314,3315,3316],{"id":3233,"depth":102,"text":3234},{"id":3240,"depth":102,"text":3241},{"id":3273,"depth":102,"text":3274},"talks","2024-09-29","Data Processing using Gemini Models - My first talk at a GDG Meetup. Join me as I explore the fascinating journey of transforming physical documents into structured JSON data using Google's Gemini Models. Learn about the challenges, solutions, and real-world applications of AI-powered document processing.","mdx",{"external":468},"/blog/from-paper-to-json-gdg-bucharest-2024",{"title":3188,"description":3319},"blog/13.from-paper-to-json - gdg-bucharest - 2024",[3326,3327,3328,3184],"python","flutter","ai","PXRz1YQeRF4jMgu3xrUvy2ubHxtQyyTAcOmE6gKMhss",{"id":3331,"title":3332,"body":3333,"category":501,"date":3421,"description":3422,"draft":468,"extension":3320,"howto":490,"image":490,"meta":3423,"navigation":117,"path":3424,"seo":3425,"stem":3426,"tags":3427,"__hash__":3431},"blog/blog/12.homelab-exposing-coolify-services-on-local-network.mdx","Coolify - Exposing services to the local network",{"type":7,"value":3334,"toc":3419},[3335,3338,3341,3346,3349,3352,3355,3362,3365,3376,3395,3398,3401,3410,3413,3416],[10,3336,3337],{},"Coolify is the new cool guy in town and while it works awesome mostly, there are still some topics not covered by documentation.. at least at the moment when this article is getting written.",[10,3339,3340],{},"So, today's topic",[3342,3343,3345],"h4",{"id":3344},"accessing-services-from-local-network-without-needing-a-domain","Accessing services from local network... without needing a domain.",[10,3347,3348],{},"By default you are asked (even though is not required) to setup a domain for your coolify instance. But this is not required and for a homelab setup where you will access services only from the local network it's ok to leave that empty... or just use anything on that field.",[10,3350,3351],{},"Assuming you get over the initial coolify setup and deployed your resource.. how can you get to it!?",[10,3353,3354],{},"Let's take as a concrete example n8n which is availabe in coolify.",[10,3356,3357,3358,3361],{},"N8N is served on port 5678 but that's available only inside the container where it is running. To be able to connect to it via ",[50,3359,3360],{},"http://{your_homelab_server_ip_address}:5678"," we need to expose the port on host.",[10,3363,3364],{},"For this we need to slightly change the docker-compose.yaml file.",[10,3366,3367,3368,3371,3372,3375],{},"Just add the ",[50,3369,3370],{},"ports"," section to the docker-compose file, on the same level as ",[50,3373,3374],{},"environment",", as bellow, save and redeploy.",[84,3377,3379],{"className":1498,"code":3378,"language":1500,"meta":89,"style":89},"ports:\n  - '5678:5678'\n",[50,3380,3381,3387],{"__ignoreMap":89},[93,3382,3383,3385],{"class":95,"line":96},[93,3384,3370],{"class":1507},[93,3386,1511],{"class":228},[93,3388,3389,3392],{"class":95,"line":102},[93,3390,3391],{"class":228},"  - ",[93,3393,3394],{"class":241},"'5678:5678'\n",[10,3396,3397],{},"and that should do it.",[10,3399,3400],{},"Quick video for it:",[1767,3402,3299,3404,3409],{"controls":117,"className":3403,"autoPlay":117,"muted":117},[3196],[3405,3406],"source",{"src":3407,"type":3408},"/videos/12-coolify-export-port.mp4","video/mp4","\n  Your browser does not support the video tag.\n",[10,3411,3412],{},"Thanks",[10,3414,3415],{},"🙏🏻",[455,3417,3418],{},"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 .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);}",{"title":89,"searchDepth":102,"depth":102,"links":3420},[],"2024-07-08","A comprehensive guide to accessing Coolify-deployed services on your local network without a domain. Learn how to configure your homelab setup for internal access, understand the networking concepts involved, and implement secure service exposure using Coolify's built-in features and best practices.",{"external":468},"/blog/homelab-exposing-coolify-services-on-local-network",{"title":3332,"description":3422},"blog/12.homelab-exposing-coolify-services-on-local-network",[60,3428,501,3429,3430],"reverse","local","network","ucbA1_JsWE470lW5LwUBBFxv4iOUEidGaqEkTGsezU0",{"id":3433,"title":3434,"body":3435,"category":501,"date":3503,"description":3504,"draft":468,"extension":469,"howto":3505,"image":490,"meta":3521,"navigation":117,"path":3522,"seo":3523,"stem":3524,"tags":3525,"__hash__":3527},"blog/blog/11.coolify-reverse-proxy-to-another-host.md","Coolify - Deploying a reverse proxy to another host",{"type":7,"value":3436,"toc":3500},[3437,3440,3443,3446,3449,3452,3459,3463,3493,3496,3498],[10,3438,3439],{},"Do you have multiple VMs deployed on your home network?",[10,3441,3442],{},"Do you need to expose each through public domains?",[10,3444,3445],{},"You only have a single public IP and don't know how to route trafic depending on domain?",[10,3447,3448],{},"Do you want Coolify do be the entrypoint handling all the trafic?",[10,3450,3451],{},"If the answer is yes or there is any other reason you might think this help, here's a repo + docker image helping you deploy\na reverse proxy with/in Coolify to expose an app on a different host.",[10,3453,3454,3455],{},"Repo: ",[1142,3456,3457],{"href":3457,"rel":3458},"https://github.com/andreitere/coolify-nginx-reverse-proxy",[1329],[1264,3460,3462],{"id":3461},"steps-roughly","Steps (roughly)",[378,3464,3465,3468,3486],{},[20,3466,3467],{},"Configure your new resource (public repository)",[20,3469,3042,3470,3473,3474,3476,3477,3480,3481,3485],{},[50,3471,3472],{},"Configuration"," -> ",[50,3475,371],{}," configure a new env variable ",[50,3478,3479],{},"PROXY_HOST"," and set its value the the host and port you want the reverse proxy to point to (eg: ",[1142,3482,3483],{"href":3483,"rel":3484},"http://10.1.1.2:3000",[1329],")",[20,3487,3488,3489,3492],{},"Make sure you ",[50,3490,3491],{},"Generate domain"," on the same page",[10,3494,3495],{},"And that's it. It should work. Feel free to fork the repo, suggest fixes, open issues, etc.",[10,3497,3412],{},[10,3499,3415],{},{"title":89,"searchDepth":102,"depth":102,"links":3501},[3502],{"id":3461,"depth":108,"text":3462},"2024-06-25","An in-depth tutorial on setting up a reverse proxy with Nginx in Coolify to expose services from different hosts. Discover how to securely manage subdomains, configure SSL certificates, and implement advanced routing techniques while maintaining the simplicity and power of Coolify's infrastructure.",{"totalTime":3506,"tool":3507,"steps":3509},"PT10M",[474,3508],"Target host with running service",[3510,3513,3516,3518],{"name":3511,"text":3512},"Create new resource in Coolify","Add a new resource using the public repository https://github.com/andreitere/coolify-nginx-reverse-proxy",{"name":3514,"text":3515},"Configure PROXY_HOST variable","Go to Configuration > Environment Variables and set PROXY_HOST to your target host and port (e.g., http://10.1.1.2:3000)",{"name":3491,"text":3517},"Click Generate domain on the same configuration page to create the public URL for your reverse proxy",{"name":3519,"text":3520},"Deploy and verify","Deploy the resource and verify the domain correctly routes traffic to your target host",{"external":468},"/blog/coolify-reverse-proxy-to-another-host",{"title":3434,"description":3504},"blog/11.coolify-reverse-proxy-to-another-host",[60,3428,2736,3526],"nginx","vpCTO2Vz27dY12JvRCuJUF_3tF4ZEfXYGPLlFmrSDxk",{"id":3529,"title":3530,"body":3531,"category":501,"date":4049,"description":4050,"draft":468,"extension":469,"howto":490,"image":490,"meta":4051,"navigation":117,"path":4052,"seo":4053,"stem":4054,"tags":4055,"__hash__":4060},"blog/blog/10.umami-portainer-traefik.md","Umami + Portainer + Traefik = 🔥",{"type":7,"value":3532,"toc":4045},[3533,3536,3539,3543,3546,3557,3569,3573,4008,4010,4012,4014,4017,4042],[10,3534,3535],{},"Hit some hurdles with self-hosting Umami with Portainer and exposing it properly\nwith Traefik... so just sharing the recipe and some tips!",[10,3537,3538],{},"This will assume you have a working Traefik + Portainer setup. You will find\nseveral tutorials on how to make that work 😅.",[1264,3540,3542],{"id":3541},"tips","Tips",[10,3544,3545],{},"Make sure you properly configure CORS middleware - besides the origin, everything else should do fine.",[10,3547,3548,3549,3552,3553,3556],{},"For me, ",[50,3550,3551],{},"existing_network"," is a bridge network where traefik is configured and from where it receives all the trafik.\nConsidering we are connecting to the Postgre instance through a different network (umami), ",[50,3554,3555],{},"traefik.docker.network"," is very\nimportant for traefik to know from which network to listen for requests.",[10,3558,3559,3560,3565,3566,339],{},"In case you are trying to collect analytics from a Nuxt site using ",[1142,3561,3564],{"href":3562,"rel":3563},"https://github.com/ijkml/nuxt-umami",[1329],"nuxt-umami"," make\nsure you configure it with ",[50,3567,3568],{},"version: 2",[3342,3570,3572],{"id":3571},"docker-composeyml","docker-compose.yml",[84,3574,3576],{"className":1498,"code":3575,"language":1500,"meta":89,"style":89},"version: \"3.7\"\nservices:\n  umami:\n    image: ghcr.io/umami-software/umami:postgresql-latest\n    environment:\n      - TRACKER_SCRIPT_NAME=getinfo\n      - APP_SECRET=random_string\n      - DATABASE_URL=${DATABASE_URL}\n      - DATABASE_TYPE=${DATABASE_TYPE}\n      - HASH_SALT=${HASH_SALT}\n      - POSTGRES_DB=${POSTGRES_DB}\n      - POSTGRES_USER=${POSTGRES_USER}\n      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}\n    ports:\n      - 33000:3000\n    labels:\n      - \"traefik.enable=true\"\n      - \"traefik.docker.network=existing_network\"\n      - \"traefik.constraint-label=existing_network\"\n      - \"traefik.http.routers.umami.rule=Host(`\u003Cyour domain>`)\"\n      - \"traefik.http.routers.umami.priority=3\"\n      - \"traefik.http.services.umami.loadbalancer.server.port=3000\"\n      - \"traefik.http.routers.umami.middlewares=traefik-https-redirect,umami_cors\"\n      - \"traefik.http.routers.umami.tls=true\"\n      - \"traefik.http.routers.umami.tls.certresolver=\u003Cyour-resolver>\"\n      - \"traefik.http.middlewares.umami_cors.headers.accesscontrolallowmethods=GET,OPTIONS,PUT\"\n      - \"traefik.http.middlewares.umami_cors.headers.accesscontrolallowheaders=*\"\n      - \"traefik.http.middlewares.umami_cors.headers.accesscontrolalloworiginlist=https://tere.ro\"\n      - \"traefik.http.middlewares.umami_cors.headers.accesscontrolmaxage=100\"\n      - \"traefik.http.middlewares.umami_cors.headers.addvaryheader=true\"\n    networks:\n      - umami\n      - existing_network\n    depends_on:\n      - db\n    restart: always\n  db:\n    image: postgres:15-alpine\n    environment:\n      - DATABASE_URL=${DATABASE_URL}\n      - DATABASE_TYPE=${DATABASE_TYPE}\n      - HASH_SALT=${HASH_SALT}\n      - POSTGRES_DB=${POSTGRES_DB}\n      - POSTGRES_USER=${POSTGRES_USER}\n      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}\n    networks:\n      - umami\n    volumes:\n      - db:/var/lib/postgresql/data\n    restart: always\nvolumes:\n  db:\nnetworks:\n  existing_network:\n    external: true\n  umami:\n    external: false\n",[50,3577,3578,3588,3595,3602,3612,3619,3627,3634,3641,3648,3655,3662,3669,3676,3683,3690,3697,3704,3711,3718,3725,3732,3739,3746,3753,3760,3767,3774,3781,3788,3795,3802,3809,3817,3825,3833,3844,3852,3862,3869,3876,3883,3890,3897,3904,3911,3918,3925,3933,3941,3950,3958,3965,3973,3981,3991,3998],{"__ignoreMap":89},[93,3579,3580,3583,3585],{"class":95,"line":96},[93,3581,3582],{"class":1507},"version",[93,3584,253],{"class":228},[93,3586,3587],{"class":241},"\"3.7\"\n",[93,3589,3590,3593],{"class":95,"line":102},[93,3591,3592],{"class":1507},"services",[93,3594,1511],{"class":228},[93,3596,3597,3600],{"class":95,"line":108},[93,3598,3599],{"class":1507},"  umami",[93,3601,1511],{"class":228},[93,3603,3604,3607,3609],{"class":95,"line":114},[93,3605,3606],{"class":1507},"    image",[93,3608,253],{"class":228},[93,3610,3611],{"class":241},"ghcr.io/umami-software/umami:postgresql-latest\n",[93,3613,3614,3617],{"class":95,"line":121},[93,3615,3616],{"class":1507},"    environment",[93,3618,1511],{"class":228},[93,3620,3621,3624],{"class":95,"line":127},[93,3622,3623],{"class":228},"      - ",[93,3625,3626],{"class":241},"TRACKER_SCRIPT_NAME=getinfo\n",[93,3628,3629,3631],{"class":95,"line":133},[93,3630,3623],{"class":228},[93,3632,3633],{"class":241},"APP_SECRET=random_string\n",[93,3635,3636,3638],{"class":95,"line":138},[93,3637,3623],{"class":228},[93,3639,3640],{"class":241},"DATABASE_URL=${DATABASE_URL}\n",[93,3642,3643,3645],{"class":95,"line":143},[93,3644,3623],{"class":228},[93,3646,3647],{"class":241},"DATABASE_TYPE=${DATABASE_TYPE}\n",[93,3649,3650,3652],{"class":95,"line":149},[93,3651,3623],{"class":228},[93,3653,3654],{"class":241},"HASH_SALT=${HASH_SALT}\n",[93,3656,3657,3659],{"class":95,"line":154},[93,3658,3623],{"class":228},[93,3660,3661],{"class":241},"POSTGRES_DB=${POSTGRES_DB}\n",[93,3663,3664,3666],{"class":95,"line":159},[93,3665,3623],{"class":228},[93,3667,3668],{"class":241},"POSTGRES_USER=${POSTGRES_USER}\n",[93,3670,3671,3673],{"class":95,"line":164},[93,3672,3623],{"class":228},[93,3674,3675],{"class":241},"POSTGRES_PASSWORD=${POSTGRES_PASSWORD}\n",[93,3677,3678,3681],{"class":95,"line":170},[93,3679,3680],{"class":1507},"    ports",[93,3682,1511],{"class":228},[93,3684,3685,3687],{"class":95,"line":176},[93,3686,3623],{"class":228},[93,3688,3689],{"class":241},"33000:3000\n",[93,3691,3692,3695],{"class":95,"line":1622},[93,3693,3694],{"class":1507},"    labels",[93,3696,1511],{"class":228},[93,3698,3699,3701],{"class":95,"line":1630},[93,3700,3623],{"class":228},[93,3702,3703],{"class":241},"\"traefik.enable=true\"\n",[93,3705,3706,3708],{"class":95,"line":1638},[93,3707,3623],{"class":228},[93,3709,3710],{"class":241},"\"traefik.docker.network=existing_network\"\n",[93,3712,3713,3715],{"class":95,"line":1646},[93,3714,3623],{"class":228},[93,3716,3717],{"class":241},"\"traefik.constraint-label=existing_network\"\n",[93,3719,3720,3722],{"class":95,"line":1654},[93,3721,3623],{"class":228},[93,3723,3724],{"class":241},"\"traefik.http.routers.umami.rule=Host(`\u003Cyour domain>`)\"\n",[93,3726,3727,3729],{"class":95,"line":1662},[93,3728,3623],{"class":228},[93,3730,3731],{"class":241},"\"traefik.http.routers.umami.priority=3\"\n",[93,3733,3734,3736],{"class":95,"line":1670},[93,3735,3623],{"class":228},[93,3737,3738],{"class":241},"\"traefik.http.services.umami.loadbalancer.server.port=3000\"\n",[93,3740,3741,3743],{"class":95,"line":1678},[93,3742,3623],{"class":228},[93,3744,3745],{"class":241},"\"traefik.http.routers.umami.middlewares=traefik-https-redirect,umami_cors\"\n",[93,3747,3748,3750],{"class":95,"line":1686},[93,3749,3623],{"class":228},[93,3751,3752],{"class":241},"\"traefik.http.routers.umami.tls=true\"\n",[93,3754,3755,3757],{"class":95,"line":1694},[93,3756,3623],{"class":228},[93,3758,3759],{"class":241},"\"traefik.http.routers.umami.tls.certresolver=\u003Cyour-resolver>\"\n",[93,3761,3762,3764],{"class":95,"line":1702},[93,3763,3623],{"class":228},[93,3765,3766],{"class":241},"\"traefik.http.middlewares.umami_cors.headers.accesscontrolallowmethods=GET,OPTIONS,PUT\"\n",[93,3768,3769,3771],{"class":95,"line":1710},[93,3770,3623],{"class":228},[93,3772,3773],{"class":241},"\"traefik.http.middlewares.umami_cors.headers.accesscontrolallowheaders=*\"\n",[93,3775,3776,3778],{"class":95,"line":1718},[93,3777,3623],{"class":228},[93,3779,3780],{"class":241},"\"traefik.http.middlewares.umami_cors.headers.accesscontrolalloworiginlist=https://tere.ro\"\n",[93,3782,3783,3785],{"class":95,"line":1726},[93,3784,3623],{"class":228},[93,3786,3787],{"class":241},"\"traefik.http.middlewares.umami_cors.headers.accesscontrolmaxage=100\"\n",[93,3789,3790,3792],{"class":95,"line":1734},[93,3791,3623],{"class":228},[93,3793,3794],{"class":241},"\"traefik.http.middlewares.umami_cors.headers.addvaryheader=true\"\n",[93,3796,3797,3800],{"class":95,"line":1748},[93,3798,3799],{"class":1507},"    networks",[93,3801,1511],{"class":228},[93,3803,3804,3806],{"class":95,"line":1759},[93,3805,3623],{"class":228},[93,3807,3808],{"class":241},"umami\n",[93,3810,3812,3814],{"class":95,"line":3811},33,[93,3813,3623],{"class":228},[93,3815,3816],{"class":241},"existing_network\n",[93,3818,3820,3823],{"class":95,"line":3819},34,[93,3821,3822],{"class":1507},"    depends_on",[93,3824,1511],{"class":228},[93,3826,3828,3830],{"class":95,"line":3827},35,[93,3829,3623],{"class":228},[93,3831,3832],{"class":241},"db\n",[93,3834,3836,3839,3841],{"class":95,"line":3835},36,[93,3837,3838],{"class":1507},"    restart",[93,3840,253],{"class":228},[93,3842,3843],{"class":241},"always\n",[93,3845,3847,3850],{"class":95,"line":3846},37,[93,3848,3849],{"class":1507},"  db",[93,3851,1511],{"class":228},[93,3853,3855,3857,3859],{"class":95,"line":3854},38,[93,3856,3606],{"class":1507},[93,3858,253],{"class":228},[93,3860,3861],{"class":241},"postgres:15-alpine\n",[93,3863,3865,3867],{"class":95,"line":3864},39,[93,3866,3616],{"class":1507},[93,3868,1511],{"class":228},[93,3870,3872,3874],{"class":95,"line":3871},40,[93,3873,3623],{"class":228},[93,3875,3640],{"class":241},[93,3877,3879,3881],{"class":95,"line":3878},41,[93,3880,3623],{"class":228},[93,3882,3647],{"class":241},[93,3884,3886,3888],{"class":95,"line":3885},42,[93,3887,3623],{"class":228},[93,3889,3654],{"class":241},[93,3891,3893,3895],{"class":95,"line":3892},43,[93,3894,3623],{"class":228},[93,3896,3661],{"class":241},[93,3898,3900,3902],{"class":95,"line":3899},44,[93,3901,3623],{"class":228},[93,3903,3668],{"class":241},[93,3905,3907,3909],{"class":95,"line":3906},45,[93,3908,3623],{"class":228},[93,3910,3675],{"class":241},[93,3912,3914,3916],{"class":95,"line":3913},46,[93,3915,3799],{"class":1507},[93,3917,1511],{"class":228},[93,3919,3921,3923],{"class":95,"line":3920},47,[93,3922,3623],{"class":228},[93,3924,3808],{"class":241},[93,3926,3928,3931],{"class":95,"line":3927},48,[93,3929,3930],{"class":1507},"    volumes",[93,3932,1511],{"class":228},[93,3934,3936,3938],{"class":95,"line":3935},49,[93,3937,3623],{"class":228},[93,3939,3940],{"class":241},"db:/var/lib/postgresql/data\n",[93,3942,3944,3946,3948],{"class":95,"line":3943},50,[93,3945,3838],{"class":1507},[93,3947,253],{"class":228},[93,3949,3843],{"class":241},[93,3951,3953,3956],{"class":95,"line":3952},51,[93,3954,3955],{"class":1507},"volumes",[93,3957,1511],{"class":228},[93,3959,3961,3963],{"class":95,"line":3960},52,[93,3962,3849],{"class":1507},[93,3964,1511],{"class":228},[93,3966,3968,3971],{"class":95,"line":3967},53,[93,3969,3970],{"class":1507},"networks",[93,3972,1511],{"class":228},[93,3974,3976,3979],{"class":95,"line":3975},54,[93,3977,3978],{"class":1507},"  existing_network",[93,3980,1511],{"class":228},[93,3982,3984,3987,3989],{"class":95,"line":3983},55,[93,3985,3986],{"class":1507},"    external",[93,3988,253],{"class":228},[93,3990,281],{"class":234},[93,3992,3994,3996],{"class":95,"line":3993},56,[93,3995,3599],{"class":1507},[93,3997,1511],{"class":228},[93,3999,4001,4003,4005],{"class":95,"line":4000},57,[93,4002,3986],{"class":1507},[93,4004,253],{"class":228},[93,4006,4007],{"class":234},"false\n",[10,4009,3415],{},[30,4011],{},[33,4013,2239],{"id":2238},[10,4015,4016],{},"If you're interested in more container orchestration and homelab guides:",[17,4018,4019,4026,4034],{},[20,4020,4021,4025],{},[45,4022,4023],{},[1142,4024,3155],{"href":3154}," - Advanced GPU passthrough and Docker runtime configurations",[20,4027,4028,4033],{},[45,4029,4030],{},[1142,4031,4032],{"href":2268},"Coolify Reverse Proxy Setup"," - Multi-host networking and deployment strategies",[20,4035,4036,4041],{},[45,4037,4038],{},[1142,4039,4040],{"href":2251},"Cloudflare R2 Backup for Coolify"," - Securing your homelab data with off-site backups",[455,4043,4044],{},"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 .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);}",{"title":89,"searchDepth":102,"depth":102,"links":4046},[4047,4048],{"id":3541,"depth":108,"text":3542},{"id":2238,"depth":102,"text":2239},"2024-03-15","A detailed walkthrough of creating a powerful analytics stack using Umami, Portainer, and Traefik. Learn how to set up privacy-focused web analytics with Umami, manage containers efficiently with Portainer, and handle routing seamlessly with Traefik. Perfect for self-hosters looking to build a robust, privacy-respecting analytics solution.",{"external":468},"/blog/umami-portainer-traefik",{"title":3530,"description":4050},"blog/10.umami-portainer-traefik",[4056,4057,4058,4059,500],"umami","portainer","traefik","privacy","NNGhS6KMbX3D8l_-j-nWFgU9TL-e06-ZKaSnSkHGW5Q",{"id":4062,"title":4063,"body":4064,"category":4799,"date":4800,"description":4801,"draft":468,"extension":469,"howto":490,"image":490,"meta":4802,"navigation":117,"path":4803,"seo":4804,"stem":4805,"tags":4806,"__hash__":4812},"blog/blog/6.pinia-setup-stores.md","Pinia - You might want to use setup stores",{"type":7,"value":4065,"toc":4792},[4066,4080,4083,4086,4090,4100,4246,4250,4259,4391,4394,4399,4402,4405,4408,4412,4416,4419,4587,4594,4598,4604,4748,4751,4754,4757,4760,4762,4766,4769,4789],[10,4067,3042,4068,4073,4074,4079],{},[1142,4069,4072],{"href":4070,"rel":4071},"https://pinia.vuejs.org/",[1329],"Pinia"," you can ",[1142,4075,4078],{"href":4076,"rel":4077},"https://pinia.vuejs.org/core-concepts/#defining-a-store",[1329],"define stores"," in two ways.",[10,4081,4082],{},"These ways are similar to Vue's Options and Composition APIS. Depending how big your store can grow, you might want to go the Composition API way.",[10,4084,4085],{},"Let's break it down.",[33,4087,4089],{"id":4088},"the-options-api-way","The Options API Way",[446,4091,4092],{},[10,4093,4094,4095],{},"Documentation: ",[1142,4096,4099],{"href":4097,"rel":4098},"https://pinia.vuejs.org/core-concepts/#option-stores",[1329],"\"Option Store\"",[84,4101,4103],{"className":2397,"code":4102,"language":2399,"meta":89,"style":89},"export const useLoading = defineStore('loading', {\n  state: () => ({ queue: [] }),\n  actions: {\n    add(el) {\n      this.queue = [...this.queue, el]\n    },\n    stop(el) {\n        this.queue = this.queue.filter(f => f != el)\n    }\n  },\n})\n",[50,4104,4105,4128,4141,4146,4159,4182,4187,4198,4233,4237,4242],{"__ignoreMap":89},[93,4106,4107,4109,4112,4115,4117,4120,4122,4125],{"class":95,"line":96},[93,4108,2436],{"class":1446},[93,4110,4111],{"class":1446}," const",[93,4113,4114],{"class":234}," useLoading",[93,4116,2578],{"class":1446},[93,4118,4119],{"class":1288}," defineStore",[93,4121,2587],{"class":228},[93,4123,4124],{"class":241},"'loading'",[93,4126,4127],{"class":228},", {\n",[93,4129,4130,4133,4136,4138],{"class":95,"line":102},[93,4131,4132],{"class":1288},"  state",[93,4134,4135],{"class":228},": () ",[93,4137,2693],{"class":1446},[93,4139,4140],{"class":228}," ({ queue: [] }),\n",[93,4142,4143],{"class":95,"line":108},[93,4144,4145],{"class":228},"  actions: {\n",[93,4147,4148,4151,4153,4156],{"class":95,"line":114},[93,4149,4150],{"class":1288},"    add",[93,4152,2587],{"class":228},[93,4154,4155],{"class":2686},"el",[93,4157,4158],{"class":228},") {\n",[93,4160,4161,4164,4167,4170,4173,4176,4179],{"class":95,"line":121},[93,4162,4163],{"class":234},"      this",[93,4165,4166],{"class":228},".queue ",[93,4168,4169],{"class":1446},"=",[93,4171,4172],{"class":228}," [",[93,4174,4175],{"class":1446},"...",[93,4177,4178],{"class":234},"this",[93,4180,4181],{"class":228},".queue, el]\n",[93,4183,4184],{"class":95,"line":127},[93,4185,4186],{"class":228},"    },\n",[93,4188,4189,4192,4194,4196],{"class":95,"line":133},[93,4190,4191],{"class":1288},"    stop",[93,4193,2587],{"class":228},[93,4195,4155],{"class":2686},[93,4197,4158],{"class":228},[93,4199,4200,4203,4205,4207,4210,4213,4216,4218,4221,4224,4227,4230],{"class":95,"line":138},[93,4201,4202],{"class":234},"        this",[93,4204,4166],{"class":228},[93,4206,4169],{"class":1446},[93,4208,4209],{"class":234}," this",[93,4211,4212],{"class":228},".queue.",[93,4214,4215],{"class":1288},"filter",[93,4217,2587],{"class":228},[93,4219,4220],{"class":2686},"f",[93,4222,4223],{"class":1446}," =>",[93,4225,4226],{"class":228}," f ",[93,4228,4229],{"class":1446},"!=",[93,4231,4232],{"class":228}," el)\n",[93,4234,4235],{"class":95,"line":143},[93,4236,1393],{"class":228},[93,4238,4239],{"class":95,"line":149},[93,4240,4241],{"class":228},"  },\n",[93,4243,4244],{"class":95,"line":154},[93,4245,2509],{"class":228},[33,4247,4249],{"id":4248},"the-composition-api-way","The Composition API Way",[446,4251,4252],{},[10,4253,4094,4254],{},[1142,4255,4258],{"href":4256,"rel":4257},"https://pinia.vuejs.org/core-concepts/#setup-stores",[1329],"\"Setup Store\"",[84,4260,4262],{"className":2397,"code":4261,"language":2399,"meta":89,"style":89},"export const [useLoading](/vue/vue3-vuex-4-loading-wait) = defineStore('loading', () => {\n  const queue = ref([])\n  function add(el) {\n    queue.value = [...queue.value, el]\n  }\n  function stop(el) {\n    queue.value = queue.value.filter(f => f != el)\n  }\n  return { queue, add }\n})\n",[50,4263,4264,4293,4308,4322,4336,4340,4353,4376,4380,4387],{"__ignoreMap":89},[93,4265,4266,4268,4270,4272,4275,4278,4280,4282,4284,4286,4289,4291],{"class":95,"line":96},[93,4267,2436],{"class":1446},[93,4269,4111],{"class":1446},[93,4271,4172],{"class":228},[93,4273,4274],{"class":234},"useLoading",[93,4276,4277],{"class":228},"](/vue/vue3-vuex-4-loading-wait) ",[93,4279,4169],{"class":1446},[93,4281,4119],{"class":1288},[93,4283,2587],{"class":228},[93,4285,4124],{"class":241},[93,4287,4288],{"class":228},", () ",[93,4290,2693],{"class":1446},[93,4292,2748],{"class":228},[93,4294,4295,4297,4300,4302,4305],{"class":95,"line":102},[93,4296,2572],{"class":1446},[93,4298,4299],{"class":234}," queue",[93,4301,2578],{"class":1446},[93,4303,4304],{"class":1288}," ref",[93,4306,4307],{"class":228},"([])\n",[93,4309,4310,4313,4316,4318,4320],{"class":95,"line":108},[93,4311,4312],{"class":1446},"  function",[93,4314,4315],{"class":1288}," add",[93,4317,2587],{"class":228},[93,4319,4155],{"class":2686},[93,4321,4158],{"class":228},[93,4323,4324,4327,4329,4331,4333],{"class":95,"line":114},[93,4325,4326],{"class":228},"    queue.value ",[93,4328,4169],{"class":1446},[93,4330,4172],{"class":228},[93,4332,4175],{"class":1446},[93,4334,4335],{"class":228},"queue.value, el]\n",[93,4337,4338],{"class":95,"line":121},[93,4339,2504],{"class":228},[93,4341,4342,4344,4347,4349,4351],{"class":95,"line":127},[93,4343,4312],{"class":1446},[93,4345,4346],{"class":1288}," stop",[93,4348,2587],{"class":228},[93,4350,4155],{"class":2686},[93,4352,4158],{"class":228},[93,4354,4355,4357,4359,4362,4364,4366,4368,4370,4372,4374],{"class":95,"line":133},[93,4356,4326],{"class":228},[93,4358,4169],{"class":1446},[93,4360,4361],{"class":228}," queue.value.",[93,4363,4215],{"class":1288},[93,4365,2587],{"class":228},[93,4367,4220],{"class":2686},[93,4369,4223],{"class":1446},[93,4371,4226],{"class":228},[93,4373,4229],{"class":1446},[93,4375,4232],{"class":228},[93,4377,4378],{"class":95,"line":138},[93,4379,2504],{"class":228},[93,4381,4382,4384],{"class":95,"line":143},[93,4383,2598],{"class":1446},[93,4385,4386],{"class":228}," { queue, add }\n",[93,4388,4389],{"class":95,"line":149},[93,4390,2509],{"class":228},[10,4392,4393],{},"The official documentation recommends starting  with the Option Store if you are undecided and that's perfectly fine. That's also what I did for a few of my apps and still finding it more comfortable if I may.",[10,4395,4396],{},[45,4397,4398],{},"But in some cases I, for one, wish I would've gone with the Setup Store.",[10,4400,4401],{},"Let's see why.",[10,4403,4404],{},"I have a rather big store, multiple actions for which I need to track in a sepparate store when the api request is done and when it finishes.",[10,4406,4407],{},"This implies using a store in another store.",[2346,4409,4411],{"id":4410},"how-you-do-this","How you do this?",[33,4413,4415],{"id":4414},"option-store","Option Store",[10,4417,4418],{},"For the option stores it can look something like this",[84,4420,4422],{"className":2397,"code":4421,"language":2399,"meta":89,"style":89},"export const useDemo = defineStore('demo', {\n  state: () => ({}),\n  actions: {\n    actionOne(el) {\n        const $loading = useLoading()\n        $loading.add(\"actionOne\")\n        /// await api call \n        $loading.stop(\"actionOne\")\n    },\n    actionTwo(el) {\n        const $loading = useLoading()\n        $loading.add(\"actionTwo\")\n        /// await api call \n        $loading.stop(\"actionTwo\")\n    },\n  },\n})\n",[50,4423,4424,4444,4455,4459,4470,4485,4501,4506,4519,4523,4534,4546,4559,4563,4575,4579,4583],{"__ignoreMap":89},[93,4425,4426,4428,4430,4433,4435,4437,4439,4442],{"class":95,"line":96},[93,4427,2436],{"class":1446},[93,4429,4111],{"class":1446},[93,4431,4432],{"class":234}," useDemo",[93,4434,2578],{"class":1446},[93,4436,4119],{"class":1288},[93,4438,2587],{"class":228},[93,4440,4441],{"class":241},"'demo'",[93,4443,4127],{"class":228},[93,4445,4446,4448,4450,4452],{"class":95,"line":102},[93,4447,4132],{"class":1288},[93,4449,4135],{"class":228},[93,4451,2693],{"class":1446},[93,4453,4454],{"class":228}," ({}),\n",[93,4456,4457],{"class":95,"line":108},[93,4458,4145],{"class":228},[93,4460,4461,4464,4466,4468],{"class":95,"line":114},[93,4462,4463],{"class":1288},"    actionOne",[93,4465,2587],{"class":228},[93,4467,4155],{"class":2686},[93,4469,4158],{"class":228},[93,4471,4472,4475,4478,4480,4482],{"class":95,"line":121},[93,4473,4474],{"class":1446},"        const",[93,4476,4477],{"class":234}," $loading",[93,4479,2578],{"class":1446},[93,4481,4114],{"class":1288},[93,4483,4484],{"class":228},"()\n",[93,4486,4487,4490,4493,4495,4498],{"class":95,"line":127},[93,4488,4489],{"class":228},"        $loading.",[93,4491,4492],{"class":1288},"add",[93,4494,2587],{"class":228},[93,4496,4497],{"class":241},"\"actionOne\"",[93,4499,4500],{"class":228},")\n",[93,4502,4503],{"class":95,"line":133},[93,4504,4505],{"class":1282},"        /// await api call \n",[93,4507,4508,4510,4513,4515,4517],{"class":95,"line":138},[93,4509,4489],{"class":228},[93,4511,4512],{"class":1288},"stop",[93,4514,2587],{"class":228},[93,4516,4497],{"class":241},[93,4518,4500],{"class":228},[93,4520,4521],{"class":95,"line":143},[93,4522,4186],{"class":228},[93,4524,4525,4528,4530,4532],{"class":95,"line":149},[93,4526,4527],{"class":1288},"    actionTwo",[93,4529,2587],{"class":228},[93,4531,4155],{"class":2686},[93,4533,4158],{"class":228},[93,4535,4536,4538,4540,4542,4544],{"class":95,"line":154},[93,4537,4474],{"class":1446},[93,4539,4477],{"class":234},[93,4541,2578],{"class":1446},[93,4543,4114],{"class":1288},[93,4545,4484],{"class":228},[93,4547,4548,4550,4552,4554,4557],{"class":95,"line":159},[93,4549,4489],{"class":228},[93,4551,4492],{"class":1288},[93,4553,2587],{"class":228},[93,4555,4556],{"class":241},"\"actionTwo\"",[93,4558,4500],{"class":228},[93,4560,4561],{"class":95,"line":164},[93,4562,4505],{"class":1282},[93,4564,4565,4567,4569,4571,4573],{"class":95,"line":170},[93,4566,4489],{"class":228},[93,4568,4512],{"class":1288},[93,4570,2587],{"class":228},[93,4572,4556],{"class":241},[93,4574,4500],{"class":228},[93,4576,4577],{"class":95,"line":176},[93,4578,4186],{"class":228},[93,4580,4581],{"class":95,"line":1622},[93,4582,4241],{"class":228},[93,4584,4585],{"class":95,"line":1630},[93,4586,2509],{"class":228},[10,4588,4589,4590,4593],{},"Will work just fine but it bothers me a lot that i have to repeat the ",[50,4591,4592],{},"const $loading = useLoading()"," each time",[33,4595,4597],{"id":4596},"setup-store-to-the-rescue","Setup Store to the rescue",[10,4599,4600,4601,4603],{},"Now this is the case where everything looks nicer.\nYou just do the ",[50,4602,4592],{}," only at the top and that's it",[84,4605,4607],{"className":2397,"code":4606,"language":2399,"meta":89,"style":89},"export const useDemo = defineStore('demo', () => {\n    const $loading = useLoading()\n    function actionOne(el) {\n        $loading.start(\"actionOne\")\n        /// await api call \n        $loading.stop(\"actionOne\")\n    }\n    function actionTwo(el) {\n        $loading.start(\"actionTwo\")\n        /// await api call \n        $loading.stop(\"actionTwo\")\n    }\n    return { actionOne, actionTwo }\n})\n",[50,4608,4609,4631,4644,4658,4671,4675,4687,4691,4704,4716,4720,4732,4736,4744],{"__ignoreMap":89},[93,4610,4611,4613,4615,4617,4619,4621,4623,4625,4627,4629],{"class":95,"line":96},[93,4612,2436],{"class":1446},[93,4614,4111],{"class":1446},[93,4616,4432],{"class":234},[93,4618,2578],{"class":1446},[93,4620,4119],{"class":1288},[93,4622,2587],{"class":228},[93,4624,4441],{"class":241},[93,4626,4288],{"class":228},[93,4628,2693],{"class":1446},[93,4630,2748],{"class":228},[93,4632,4633,4636,4638,4640,4642],{"class":95,"line":102},[93,4634,4635],{"class":1446},"    const",[93,4637,4477],{"class":234},[93,4639,2578],{"class":1446},[93,4641,4114],{"class":1288},[93,4643,4484],{"class":228},[93,4645,4646,4649,4652,4654,4656],{"class":95,"line":108},[93,4647,4648],{"class":1446},"    function",[93,4650,4651],{"class":1288}," actionOne",[93,4653,2587],{"class":228},[93,4655,4155],{"class":2686},[93,4657,4158],{"class":228},[93,4659,4660,4662,4665,4667,4669],{"class":95,"line":114},[93,4661,4489],{"class":228},[93,4663,4664],{"class":1288},"start",[93,4666,2587],{"class":228},[93,4668,4497],{"class":241},[93,4670,4500],{"class":228},[93,4672,4673],{"class":95,"line":121},[93,4674,4505],{"class":1282},[93,4676,4677,4679,4681,4683,4685],{"class":95,"line":127},[93,4678,4489],{"class":228},[93,4680,4512],{"class":1288},[93,4682,2587],{"class":228},[93,4684,4497],{"class":241},[93,4686,4500],{"class":228},[93,4688,4689],{"class":95,"line":133},[93,4690,1393],{"class":228},[93,4692,4693,4695,4698,4700,4702],{"class":95,"line":138},[93,4694,4648],{"class":1446},[93,4696,4697],{"class":1288}," actionTwo",[93,4699,2587],{"class":228},[93,4701,4155],{"class":2686},[93,4703,4158],{"class":228},[93,4705,4706,4708,4710,4712,4714],{"class":95,"line":143},[93,4707,4489],{"class":228},[93,4709,4664],{"class":1288},[93,4711,2587],{"class":228},[93,4713,4556],{"class":241},[93,4715,4500],{"class":228},[93,4717,4718],{"class":95,"line":149},[93,4719,4505],{"class":1282},[93,4721,4722,4724,4726,4728,4730],{"class":95,"line":154},[93,4723,4489],{"class":228},[93,4725,4512],{"class":1288},[93,4727,2587],{"class":228},[93,4729,4556],{"class":241},[93,4731,4500],{"class":228},[93,4733,4734],{"class":95,"line":159},[93,4735,1393],{"class":228},[93,4737,4738,4741],{"class":95,"line":164},[93,4739,4740],{"class":1446},"    return",[93,4742,4743],{"class":228}," { actionOne, actionTwo }\n",[93,4745,4746],{"class":95,"line":170},[93,4747,2509],{"class":228},[10,4749,4750],{},"And looks already much cleaner.",[10,4752,4753],{},"So yeah, if you're into cleaner looking code you might want to got the \"Setup\" way when writing Pinia Stores.",[10,4755,4756],{},"BRB, refactoring. 🚀",[10,4758,4759],{},"Thank you.",[30,4761],{},[33,4763,4765],{"id":4764},"related-vue-development-articles","Related Vue Development Articles",[10,4767,4768],{},"For more Vue 3 state management and tooling guides:",[17,4770,4771,4780],{},[20,4772,4773,4779],{},[45,4774,4775],{},[1142,4776,4778],{"href":4777},"/vue/vue3-vuex-4-loading-wait","Vuex4 Loading States Module"," - If you're still using Vuex and want to see the differences",[20,4781,4782,4788],{},[45,4783,4784],{},[1142,4785,4787],{"href":4786},"/development/vite-s-proxy-overlooked-feature","Vite's Proxy Configuration"," - Essential development environment setup for Vue apps",[455,4790,4791],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}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 .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 .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":89,"searchDepth":102,"depth":102,"links":4793},[4794,4795,4796,4797,4798],{"id":4088,"depth":102,"text":4089},{"id":4248,"depth":102,"text":4249},{"id":4414,"depth":102,"text":4415},{"id":4596,"depth":102,"text":4597},{"id":4764,"depth":102,"text":4765},"vue","2023-02-15","A practical comparison of Pinia's Options API vs Composition API store patterns, with real-world examples showing how setup stores can lead to cleaner, more maintainable code.",{"external":468,"lastmod":2298},"/blog/pinia-setup-stores",{"title":4063,"description":4801},"blog/6.pinia-setup-stores",[4807,4808,4809,4810,4811],"vuejs","vue3","pinia","wweb","javascript","Zqr48cSxWDh7_Rc5j0ND3o7t6OL2K7l14e9vqxzLja4",{"id":4814,"title":4815,"body":4816,"category":502,"date":5353,"description":5354,"draft":468,"extension":469,"howto":5355,"image":490,"meta":5379,"navigation":117,"path":5380,"seo":5381,"stem":5382,"tags":5383,"__hash__":5387},"blog/blog/5.gitlab-ci-telegram-notifications.md","Gitlab CI Telegram Notifications",{"type":7,"value":4817,"toc":5347},[4818,4826,4829,4833,4836,4840,4853,4862,4866,4869,4885,4888,4901,4904,4907,5209,5222,5226,5233,5286,5290,5293,5296,5299,5330,5335,5338,5341,5344],[10,4819,4820,4821,4825],{},"I keep most of my personal projects on ",[1142,4822,4823],{"href":4823,"rel":4824},"https://gitlab.com",[1329],". While I love the ci-cd approach and how this can be defined, I hate that I cannot get an overall view of all the pipleines and their status.",[10,4827,4828],{},"It happens quite often that I trigger deployments on multiple projects and I need to check the status for each. And the only way to do this is by navigating to each individual project and go to CI/CD -> Pipelines.",[2346,4830,4832],{"id":4831},"telegram-bot-to-the-rescue","Telegram Bot to the rescue",[10,4834,4835],{},"As an active Telegram user, simplifying the process with a bot seemed natural. I'm looking for something simple: a conversion with the bot where \"it\"\ncan message me when a pipeline progesses.",[33,4837,4839],{"id":4838},"creating-the-bot","Creating the bot.",[10,4841,4842,4843,4848,4849,4852],{},"You can follow ",[1142,4844,4847],{"href":4845,"rel":4846},"https://learn.microsoft.com/en-us/azure/bot-service/bot-service-channel-connect-telegram?view=azure-bot-service-4.0#create-a-new-telegram-bot-with-botfather",[1329],"this tutorial"," to quickly get a bot up and running and get your bot ",[2918,4850,4851],{},"access token",". You will need this when calling the needed APIs",[10,4854,4855,4856,4861],{},"With ",[1142,4857,4860],{"href":4858,"rel":4859},"https://telegram.me/botfather",[1329],"@BotFather"," you can also tweak other settings of your bot. You might want to change privacy regarding groups and so on, depending on how you plan to use it",[33,4863,4865],{"id":4864},"setting-up-the-conversion","Setting up the conversion",[10,4867,4868],{},"For the bot to be able to send messages you will need a chat id.",[378,4870,4871,4878],{},[20,4872,4873,4874,4877],{},"Open a conversion with your newly created bot and send the ",[2918,4875,4876],{},"/start"," command.",[20,4879,4880,4881,4884],{},"Next we need to call the ",[2918,4882,4883],{},"getUpdates"," endpoint of the bot to get the messages send to your bot. This will help us finding the chat id.",[10,4886,4887],{},"Use your prefferred rest client (i'm using Insomnia) and execut a POST Request to",[84,4889,4891],{"className":219,"code":4890,"language":221,"meta":89,"style":89},"POST https://api.telegram.org/bot$ACCESS_TOKEN/getUpdates\n",[50,4892,4893],{"__ignoreMap":89},[93,4894,4895,4898],{"class":95,"line":96},[93,4896,4897],{"class":228},"POST https:",[93,4899,4900],{"class":1282},"//api.telegram.org/bot$ACCESS_TOKEN/getUpdates\n",[10,4902,4903],{},"Make sure you replace $ACCESS_TOKEN with your own.",[10,4905,4906],{},"Response should look structuraly similar.",[84,4908,4910],{"className":219,"code":4909,"language":221,"meta":89,"style":89},"{\n  \"ok\": true,\n  \"result\": [\n    {\n      \"update_id\": 785513355,\n      \"message\": {\n        \"message_id\": 19,\n        \"from\": {\n          \"id\": 213456691,\n          \"is_bot\": false,\n          \"first_name\": \"**redacted**\",\n          \"last_name\": \"**redacted**\",\n          \"username\": \"**redacted**\",\n          \"language_code\": \"en\"\n        },\n        \"chat\": {\n          \"id\": 213456691,\n          \"first_name\": \"**redacted**\",\n          \"last_name\": \"Tere\",\n          \"username\": \"**redacted**\",\n          \"type\": \"private\"\n        },\n        \"date\": 1676219181,\n        \"text\": \"/start\",\n        \"entities\": [\n          {\n            \"offset\": 0,\n            \"length\": 6,\n            \"type\": \"bot_command\"\n          }\n        ]\n      }\n    }\n  ]\n}\n",[50,4911,4912,4916,4927,4935,4940,4952,4959,4971,4978,4990,5001,5013,5024,5035,5045,5050,5057,5067,5077,5088,5098,5108,5112,5124,5136,5143,5148,5160,5172,5182,5187,5192,5196,5200,5205],{"__ignoreMap":89},[93,4913,4914],{"class":95,"line":96},[93,4915,229],{"class":228},[93,4917,4918,4921,4923,4925],{"class":95,"line":102},[93,4919,4920],{"class":234},"  \"ok\"",[93,4922,253],{"class":228},[93,4924,2673],{"class":234},[93,4926,259],{"class":228},[93,4928,4929,4932],{"class":95,"line":108},[93,4930,4931],{"class":234},"  \"result\"",[93,4933,4934],{"class":228},": [\n",[93,4936,4937],{"class":95,"line":114},[93,4938,4939],{"class":228},"    {\n",[93,4941,4942,4945,4947,4950],{"class":95,"line":121},[93,4943,4944],{"class":234},"      \"update_id\"",[93,4946,253],{"class":228},[93,4948,4949],{"class":234},"785513355",[93,4951,259],{"class":228},[93,4953,4954,4957],{"class":95,"line":127},[93,4955,4956],{"class":234},"      \"message\"",[93,4958,1358],{"class":228},[93,4960,4961,4964,4966,4969],{"class":95,"line":133},[93,4962,4963],{"class":234},"        \"message_id\"",[93,4965,253],{"class":228},[93,4967,4968],{"class":234},"19",[93,4970,259],{"class":228},[93,4972,4973,4976],{"class":95,"line":138},[93,4974,4975],{"class":234},"        \"from\"",[93,4977,1358],{"class":228},[93,4979,4980,4983,4985,4988],{"class":95,"line":143},[93,4981,4982],{"class":234},"          \"id\"",[93,4984,253],{"class":228},[93,4986,4987],{"class":234},"213456691",[93,4989,259],{"class":228},[93,4991,4992,4995,4997,4999],{"class":95,"line":149},[93,4993,4994],{"class":234},"          \"is_bot\"",[93,4996,253],{"class":228},[93,4998,269],{"class":234},[93,5000,259],{"class":228},[93,5002,5003,5006,5008,5011],{"class":95,"line":154},[93,5004,5005],{"class":234},"          \"first_name\"",[93,5007,253],{"class":228},[93,5009,5010],{"class":241},"\"**redacted**\"",[93,5012,259],{"class":228},[93,5014,5015,5018,5020,5022],{"class":95,"line":159},[93,5016,5017],{"class":234},"          \"last_name\"",[93,5019,253],{"class":228},[93,5021,5010],{"class":241},[93,5023,259],{"class":228},[93,5025,5026,5029,5031,5033],{"class":95,"line":164},[93,5027,5028],{"class":234},"          \"username\"",[93,5030,253],{"class":228},[93,5032,5010],{"class":241},[93,5034,259],{"class":228},[93,5036,5037,5040,5042],{"class":95,"line":170},[93,5038,5039],{"class":234},"          \"language_code\"",[93,5041,253],{"class":228},[93,5043,5044],{"class":241},"\"en\"\n",[93,5046,5047],{"class":95,"line":176},[93,5048,5049],{"class":228},"        },\n",[93,5051,5052,5055],{"class":95,"line":1622},[93,5053,5054],{"class":234},"        \"chat\"",[93,5056,1358],{"class":228},[93,5058,5059,5061,5063,5065],{"class":95,"line":1630},[93,5060,4982],{"class":234},[93,5062,253],{"class":228},[93,5064,4987],{"class":234},[93,5066,259],{"class":228},[93,5068,5069,5071,5073,5075],{"class":95,"line":1638},[93,5070,5005],{"class":234},[93,5072,253],{"class":228},[93,5074,5010],{"class":241},[93,5076,259],{"class":228},[93,5078,5079,5081,5083,5086],{"class":95,"line":1646},[93,5080,5017],{"class":234},[93,5082,253],{"class":228},[93,5084,5085],{"class":241},"\"Tere\"",[93,5087,259],{"class":228},[93,5089,5090,5092,5094,5096],{"class":95,"line":1654},[93,5091,5028],{"class":234},[93,5093,253],{"class":228},[93,5095,5010],{"class":241},[93,5097,259],{"class":228},[93,5099,5100,5103,5105],{"class":95,"line":1662},[93,5101,5102],{"class":234},"          \"type\"",[93,5104,253],{"class":228},[93,5106,5107],{"class":241},"\"private\"\n",[93,5109,5110],{"class":95,"line":1670},[93,5111,5049],{"class":228},[93,5113,5114,5117,5119,5122],{"class":95,"line":1678},[93,5115,5116],{"class":234},"        \"date\"",[93,5118,253],{"class":228},[93,5120,5121],{"class":234},"1676219181",[93,5123,259],{"class":228},[93,5125,5126,5129,5131,5134],{"class":95,"line":1686},[93,5127,5128],{"class":234},"        \"text\"",[93,5130,253],{"class":228},[93,5132,5133],{"class":241},"\"/start\"",[93,5135,259],{"class":228},[93,5137,5138,5141],{"class":95,"line":1694},[93,5139,5140],{"class":234},"        \"entities\"",[93,5142,4934],{"class":228},[93,5144,5145],{"class":95,"line":1702},[93,5146,5147],{"class":228},"          {\n",[93,5149,5150,5153,5155,5158],{"class":95,"line":1710},[93,5151,5152],{"class":234},"            \"offset\"",[93,5154,253],{"class":228},[93,5156,5157],{"class":234},"0",[93,5159,259],{"class":228},[93,5161,5162,5165,5167,5170],{"class":95,"line":1718},[93,5163,5164],{"class":234},"            \"length\"",[93,5166,253],{"class":228},[93,5168,5169],{"class":234},"6",[93,5171,259],{"class":228},[93,5173,5174,5177,5179],{"class":95,"line":1726},[93,5175,5176],{"class":234},"            \"type\"",[93,5178,253],{"class":228},[93,5180,5181],{"class":241},"\"bot_command\"\n",[93,5183,5184],{"class":95,"line":1734},[93,5185,5186],{"class":228},"          }\n",[93,5188,5189],{"class":95,"line":1748},[93,5190,5191],{"class":228},"        ]\n",[93,5193,5194],{"class":95,"line":1759},[93,5195,2495],{"class":228},[93,5197,5198],{"class":95,"line":3811},[93,5199,1393],{"class":228},[93,5201,5202],{"class":95,"line":3819},[93,5203,5204],{"class":228},"  ]\n",[93,5206,5207],{"class":95,"line":3827},[93,5208,111],{"class":228},[378,5210,5211],{"start":108},[20,5212,5213,5214,5217,5218,5221],{},"Note down the value for ",[50,5215,5216],{},"message.from.id",". This is the ",[50,5219,5220],{},"chat id"," were gonna use for sending updates",[33,5223,5225],{"id":5224},"sending-messages-with-using-the-rest-api","Sending messages with using the REST API",[10,5227,5228,5229,5232],{},"To send a message to a chat you will need to call the ",[2918,5230,5231],{},"sendMessage"," endpoint.",[84,5234,5236],{"className":219,"code":5235,"language":221,"meta":89,"style":89},"POST https://api.telegram.org/bot$ACCESS_TOKEN/sendMessage\n{\n    \"chat_id\": \"213456691\",\n    \"text\": \"hey from bot\",\n    \"disable_notification\": true\n}\n",[50,5237,5238,5245,5249,5261,5273,5282],{"__ignoreMap":89},[93,5239,5240,5242],{"class":95,"line":96},[93,5241,4897],{"class":228},[93,5243,5244],{"class":1282},"//api.telegram.org/bot$ACCESS_TOKEN/sendMessage\n",[93,5246,5247],{"class":95,"line":102},[93,5248,229],{"class":228},[93,5250,5251,5254,5256,5259],{"class":95,"line":108},[93,5252,5253],{"class":234},"    \"chat_id\"",[93,5255,253],{"class":228},[93,5257,5258],{"class":241},"\"213456691\"",[93,5260,259],{"class":228},[93,5262,5263,5266,5268,5271],{"class":95,"line":114},[93,5264,5265],{"class":234},"    \"text\"",[93,5267,253],{"class":228},[93,5269,5270],{"class":241},"\"hey from bot\"",[93,5272,259],{"class":228},[93,5274,5275,5278,5280],{"class":95,"line":121},[93,5276,5277],{"class":234},"    \"disable_notification\"",[93,5279,253],{"class":228},[93,5281,281],{"class":234},[93,5283,5284],{"class":95,"line":127},[93,5285,111],{"class":228},[33,5287,5289],{"id":5288},"integrating-into-gitlab-ciyml","Integrating into .gitlab-ci.yml",[10,5291,5292],{},"Now it's up to you on which stages you want to be notified one.",[10,5294,5295],{},"In the stage's script you just need to execut the REST call with whatever text you want.",[10,5297,5298],{},"For this I'm using curl and it looks something like this:",[84,5300,5302],{"className":1498,"code":5301,"language":1500,"meta":89,"style":89},"build:\n  script:\n    - |\n      curl --request POST --url https://api.telegram.org/bot$ACCESS_TOKEN/sendMessage --header 'Content-Type: application/json' --data '{\"chat_id\": \"213456691\",\"text\": \"'\"Building  [${CI_PROJECT_NAME}] for environment: [${TARGET}] from branch: [${CI_COMMIT_BRANCH}] 🚀\"'\",\"disable_notification\": true}'\n",[50,5303,5304,5311,5318,5325],{"__ignoreMap":89},[93,5305,5306,5309],{"class":95,"line":96},[93,5307,5308],{"class":1507},"build",[93,5310,1511],{"class":228},[93,5312,5313,5316],{"class":95,"line":102},[93,5314,5315],{"class":1507},"  script",[93,5317,1511],{"class":228},[93,5319,5320,5322],{"class":95,"line":108},[93,5321,1553],{"class":228},[93,5323,5324],{"class":1446},"|\n",[93,5326,5327],{"class":95,"line":114},[93,5328,5329],{"class":241},"      curl --request POST --url https://api.telegram.org/bot$ACCESS_TOKEN/sendMessage --header 'Content-Type: application/json' --data '{\"chat_id\": \"213456691\",\"text\": \"'\"Building  [${CI_PROJECT_NAME}] for environment: [${TARGET}] from branch: [${CI_COMMIT_BRANCH}] 🚀\"'\",\"disable_notification\": true}'\n",[446,5331,5332],{},[10,5333,5334],{},"Make sure you replace the $ACCESS_TOKEN. Also chat_id value. You should even add them to your CI/CD Variables",[10,5336,5337],{},"And that's it. Now you will have better view over your jobs.",[10,5339,5340],{},"That's it ��. Thank you",[10,5342,5343],{},"🙏🏼",[455,5345,5346],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":89,"searchDepth":102,"depth":102,"links":5348},[5349,5350,5351,5352],{"id":4838,"depth":102,"text":4839},{"id":4864,"depth":102,"text":4865},{"id":5224,"depth":102,"text":5225},{"id":5288,"depth":102,"text":5289},"2023-02-12","Streamline your GitLab CI/CD pipeline monitoring with real-time Telegram notifications, making it easier to track multiple project deployments from a single chat interface.",{"totalTime":471,"tool":5356,"steps":5360},[5357,5358,5359],"GitLab account","Telegram account","REST client (Insomnia, Postman, or curl)",[5361,5364,5367,5370,5373,5376],{"name":5362,"text":5363},"Create a Telegram bot","Open Telegram, search for @BotFather, send /newbot command, and follow the prompts to create your bot. Save the access token provided.",{"name":5365,"text":5366},"Start conversation with your bot","Find your new bot in Telegram and send the /start command to initiate a conversation.",{"name":5368,"text":5369},"Get your chat ID","Make a POST request to the Telegram getUpdates API endpoint and note the message.from.id value from the response.",{"name":5371,"text":5372},"Test sending a message","Send a POST request to the Telegram sendMessage API with chat_id and text in the JSON body to verify it works.",{"name":5374,"text":5375},"Add notification to GitLab CI","In your .gitlab-ci.yml, add a curl command in the script section to call the Telegram sendMessage API with your pipeline status information.",{"name":5377,"text":5378},"Configure CI/CD variables","Add ACCESS_TOKEN and CHAT_ID as protected CI/CD variables in your GitLab project settings for security.",{"external":468},"/blog/gitlab-ci-telegram-notifications",{"title":4815,"description":5354},"blog/5.gitlab-ci-telegram-notifications",[5384,5385,5386],"gitlab","ci","telegram","zUqIX-QtLuZbktlu_YICzk5aO7zOe6sSeBDMeGM45us",{"id":5389,"title":5390,"body":5391,"category":4799,"date":6286,"description":6287,"draft":468,"extension":469,"howto":6288,"image":490,"meta":6317,"navigation":117,"path":6318,"seo":6319,"stem":6320,"tags":6321,"__hash__":6327},"blog/blog/4.firebase-web-notifications-tutorial-2023.md","Firebase Web Notifications - How to set it up and receive notifications",{"type":7,"value":5392,"toc":6277},[5393,5406,5410,5413,5417,5433,5436,5443,5449,5455,5630,5641,5645,5651,5663,5670,5674,5678,5681,5684,5693,5696,5705,5708,5938,5941,5993,6009,6013,6020,6023,6028,6200,6206,6253,6260,6264,6267,6274],[446,5394,5395,5400],{},[5396,5397,5399],"h5",{"id":5398},"tldr-here-is-the-repository-with-the-code","TLDR; Here is the repository with the code",[10,5401,5402],{},[1142,5403,5404],{"href":5404,"rel":5405},"https://github.com/andreitere/firebase-web-notifications",[1329],[2346,5407,5409],{"id":5408},"introduction","Introduction",[10,5411,5412],{},"What initially proved to be simple from a setup perspective for local development, generating the prod bundle involved some challenges.\nThe basic steps are straightforward, but productionizing the setup a bit was tricky.",[33,5414,5416],{"id":5415},"setting-up-the-firebase-project","Setting up the firebase project",[378,5418,5419,5426],{},[20,5420,5421],{},[1142,5422,5425],{"href":5423,"rel":5424},"https://firebase.google.com/docs/web/setup#create-project",[1329],"Create your firebase project",[20,5427,5428],{},[1142,5429,5432],{"href":5430,"rel":5431},"https://firebase.google.com/docs/web/setup#register-app",[1329],"Register your app",[10,5434,5435],{},"At this point you will have the firebaseConfig object with all the require credentials.",[10,5437,5438,5439,5442],{},"As I am using vite and you will probably also want to, i'm filling an ",[45,5440,5441],{},".env"," file with all the settings so that they will be filled properly on build.",[84,5444,5447],{"className":5445,"code":5446,"language":211},[209],"VITE_APP_FIREBASE_API_KEY=\"apiKey\"\nVITE_APP_PROJECT_ID=\"projectId\"\nVITE_APP_FIREBASE_MESSAGING_SENDER_ID=\"messagingSenderId\"\nVITE_APP_FIREBASE_APP_ID=\"appId\"\nVITE_APP_FIREBASE_VAPKEY=\"VAPKEY\"\nVITE_APP_FIREBASE_MEASUREMENT_ID=\"measurementId\"\nVITE_APP_FIREBASE_STORAGE_BUCKET=\"storageBucket\"\nVITE_APP_FIREABASE_DATABASE_URL=\"databaseURL\"\nVITE_APP_FIREABASE_AUTH_DOMAIN=\"authDomain\"\n",[50,5448,5446],{"__ignoreMap":89},[10,5450,5451,5452],{},"These all will be replaced in ",[50,5453,5454],{},"firebase.config.js",[84,5456,5458],{"className":2397,"code":5457,"language":2399,"meta":89,"style":89},"const firebaseConfig = {\n  apiKey: import.meta.env.VITE_APP_FIREBASE_API_KEY,\n  authDomain: import.meta.env.VITE_APP_FIREABASE_AUTH_DOMAIN,\n  databaseURL: import.meta.env.VITE_APP_FIREABASE_DATABASE_URL,\n  projectId: import.meta.env.VITE_APP_PROJECT_ID,\n  storageBucket: import.meta.env.VITE_APP_FIREBASE_STORAGE_BUCKET,\n  messagingSenderId: import.meta.env.VITE_APP_FIREBASE_MESSAGING_SENDER_ID,\n  appId: import.meta.env.VITE_APP_FIREBASE_APP_ID,\n  measurementId: import.meta.env.VITE_APP_FIREBASE_MEASUREMENT_ID\n};\nexport default firebaseConfig;\n",[50,5459,5460,5472,5492,5510,5528,5546,5564,5582,5600,5616,5621],{"__ignoreMap":89},[93,5461,5462,5465,5468,5470],{"class":95,"line":96},[93,5463,5464],{"class":1446},"const",[93,5466,5467],{"class":234}," firebaseConfig",[93,5469,2578],{"class":1446},[93,5471,2748],{"class":228},[93,5473,5474,5477,5479,5481,5484,5487,5490],{"class":95,"line":102},[93,5475,5476],{"class":228},"  apiKey: ",[93,5478,2406],{"class":1446},[93,5480,339],{"class":228},[93,5482,5483],{"class":234},"meta",[93,5485,5486],{"class":228},".env.",[93,5488,5489],{"class":234},"VITE_APP_FIREBASE_API_KEY",[93,5491,259],{"class":228},[93,5493,5494,5497,5499,5501,5503,5505,5508],{"class":95,"line":108},[93,5495,5496],{"class":228},"  authDomain: ",[93,5498,2406],{"class":1446},[93,5500,339],{"class":228},[93,5502,5483],{"class":234},[93,5504,5486],{"class":228},[93,5506,5507],{"class":234},"VITE_APP_FIREABASE_AUTH_DOMAIN",[93,5509,259],{"class":228},[93,5511,5512,5515,5517,5519,5521,5523,5526],{"class":95,"line":114},[93,5513,5514],{"class":228},"  databaseURL: ",[93,5516,2406],{"class":1446},[93,5518,339],{"class":228},[93,5520,5483],{"class":234},[93,5522,5486],{"class":228},[93,5524,5525],{"class":234},"VITE_APP_FIREABASE_DATABASE_URL",[93,5527,259],{"class":228},[93,5529,5530,5533,5535,5537,5539,5541,5544],{"class":95,"line":121},[93,5531,5532],{"class":228},"  projectId: ",[93,5534,2406],{"class":1446},[93,5536,339],{"class":228},[93,5538,5483],{"class":234},[93,5540,5486],{"class":228},[93,5542,5543],{"class":234},"VITE_APP_PROJECT_ID",[93,5545,259],{"class":228},[93,5547,5548,5551,5553,5555,5557,5559,5562],{"class":95,"line":127},[93,5549,5550],{"class":228},"  storageBucket: ",[93,5552,2406],{"class":1446},[93,5554,339],{"class":228},[93,5556,5483],{"class":234},[93,5558,5486],{"class":228},[93,5560,5561],{"class":234},"VITE_APP_FIREBASE_STORAGE_BUCKET",[93,5563,259],{"class":228},[93,5565,5566,5569,5571,5573,5575,5577,5580],{"class":95,"line":133},[93,5567,5568],{"class":228},"  messagingSenderId: ",[93,5570,2406],{"class":1446},[93,5572,339],{"class":228},[93,5574,5483],{"class":234},[93,5576,5486],{"class":228},[93,5578,5579],{"class":234},"VITE_APP_FIREBASE_MESSAGING_SENDER_ID",[93,5581,259],{"class":228},[93,5583,5584,5587,5589,5591,5593,5595,5598],{"class":95,"line":138},[93,5585,5586],{"class":228},"  appId: ",[93,5588,2406],{"class":1446},[93,5590,339],{"class":228},[93,5592,5483],{"class":234},[93,5594,5486],{"class":228},[93,5596,5597],{"class":234},"VITE_APP_FIREBASE_APP_ID",[93,5599,259],{"class":228},[93,5601,5602,5605,5607,5609,5611,5613],{"class":95,"line":143},[93,5603,5604],{"class":228},"  measurementId: ",[93,5606,2406],{"class":1446},[93,5608,339],{"class":228},[93,5610,5483],{"class":234},[93,5612,5486],{"class":228},[93,5614,5615],{"class":234},"VITE_APP_FIREBASE_MEASUREMENT_ID\n",[93,5617,5618],{"class":95,"line":149},[93,5619,5620],{"class":228},"};\n",[93,5622,5623,5625,5627],{"class":95,"line":154},[93,5624,2436],{"class":1446},[93,5626,2439],{"class":1446},[93,5628,5629],{"class":228}," firebaseConfig;\n",[10,5631,5632,5633,5636,5637,5640],{},"The ",[2918,5634,5635],{},"VAPKEY"," is not included in the firebaseConfig object. You need to get it by generating ",[50,5638,5639],{},"Web Push Certificates"," pair for the Web Configuration under your firebase project -> Project settings -> Cloud Messaging",[33,5642,5644],{"id":5643},"using-the-repository","Using the repository",[10,5646,5647,5648,5650],{},"After you filled the ",[50,5649,5441],{}," file as per the template you can install the dependencies in the project and run it",[378,5652,5653,5658],{},[20,5654,5655],{},[50,5656,5657],{},"pnpm install",[20,5659,5660],{},[50,5661,5662],{},"pnpm run dev",[10,5664,5665,5666,5669],{},"Clicking the ",[2918,5667,5668],{},"Subscribe"," button will print the token needed when sending notifications",[33,5671,5673],{"id":5672},"the-challenges","The challenges",[1264,5675,5677],{"id":5676},"firebase-service-worker","Firebase service worker",[10,5679,5680],{},"An important piece of the whole setup is the firebase-messaging-sw.js file. The service worker used by firebase.",[10,5682,5683],{},"Normally you would just put into the public folder with all the credentials in it and then it will be loaded automatically. But because I like to struggle, i did it differently.",[10,5685,5686,5687,5689,5690,339],{},"I wanted all the credentials to be pulled from the ",[50,5688,5441],{}," file. With vite you grav them with ",[50,5691,5692],{},"import.meta.env.VITE_APP_VAR",[10,5694,5695],{},"Issue with having the file in the public folder is that you will be limited to what you can import or use. So you need to do it differently",[10,5697,5698,5701,5702],{},[2918,5699,5700],{},"Solution",": Manually registering the service worker with ",[50,5703,5704],{},"{ type: \"module\" }",[10,5706,5707],{},"This way you can keep it in your root/src folder and get it compiled. This way it get's properly processed by vite and everything is setup correctly.",[84,5709,5711],{"className":2397,"code":5710,"language":2399,"meta":89,"style":89},"// firebase-messaging-sw.js\nimport { initializeApp } from \"firebase/app\";\nimport { getMessaging } from \"firebase/messaging/sw\";\nimport { onBackgroundMessage } from \"firebase/messaging/sw\";\n\nimport config from \"./firebase.config.js\";\nconst app = initializeApp(config);\nconst messaging = getMessaging();\n\nonBackgroundMessage(messaging, (payload) => {\n  console.log(payload);\n  console.log(\n    \"[firebase-messaging-sw.js] Received background message \",\n    payload\n  );\n  // Customize notification here\n  const notificationTitle = \"Background Message Title\";\n  const notificationOptions = {\n    body: \"Background Message body.\",\n    icon: \"/vite.svg\"\n  };\n\n  self.registration.showNotification(notificationTitle, notificationOptions);\n});\n",[50,5712,5713,5718,5733,5747,5760,5764,5778,5793,5807,5811,5828,5839,5848,5855,5860,5865,5870,5884,5895,5905,5913,5918,5922,5933],{"__ignoreMap":89},[93,5714,5715],{"class":95,"line":96},[93,5716,5717],{"class":1282},"// firebase-messaging-sw.js\n",[93,5719,5720,5722,5725,5727,5730],{"class":95,"line":102},[93,5721,2406],{"class":1446},[93,5723,5724],{"class":228}," { initializeApp } ",[93,5726,2412],{"class":1446},[93,5728,5729],{"class":241}," \"firebase/app\"",[93,5731,5732],{"class":228},";\n",[93,5734,5735,5737,5740,5742,5745],{"class":95,"line":108},[93,5736,2406],{"class":1446},[93,5738,5739],{"class":228}," { getMessaging } ",[93,5741,2412],{"class":1446},[93,5743,5744],{"class":241}," \"firebase/messaging/sw\"",[93,5746,5732],{"class":228},[93,5748,5749,5751,5754,5756,5758],{"class":95,"line":114},[93,5750,2406],{"class":1446},[93,5752,5753],{"class":228}," { onBackgroundMessage } ",[93,5755,2412],{"class":1446},[93,5757,5744],{"class":241},[93,5759,5732],{"class":228},[93,5761,5762],{"class":95,"line":121},[93,5763,118],{"emptyLinePlaceholder":117},[93,5765,5766,5768,5771,5773,5776],{"class":95,"line":127},[93,5767,2406],{"class":1446},[93,5769,5770],{"class":228}," config ",[93,5772,2412],{"class":1446},[93,5774,5775],{"class":241}," \"./firebase.config.js\"",[93,5777,5732],{"class":228},[93,5779,5780,5782,5785,5787,5790],{"class":95,"line":133},[93,5781,5464],{"class":1446},[93,5783,5784],{"class":234}," app",[93,5786,2578],{"class":1446},[93,5788,5789],{"class":1288}," initializeApp",[93,5791,5792],{"class":228},"(config);\n",[93,5794,5795,5797,5800,5802,5805],{"class":95,"line":138},[93,5796,5464],{"class":1446},[93,5798,5799],{"class":234}," messaging",[93,5801,2578],{"class":1446},[93,5803,5804],{"class":1288}," getMessaging",[93,5806,2606],{"class":228},[93,5808,5809],{"class":95,"line":143},[93,5810,118],{"emptyLinePlaceholder":117},[93,5812,5813,5816,5819,5822,5824,5826],{"class":95,"line":149},[93,5814,5815],{"class":1288},"onBackgroundMessage",[93,5817,5818],{"class":228},"(messaging, (",[93,5820,5821],{"class":2686},"payload",[93,5823,2690],{"class":228},[93,5825,2693],{"class":1446},[93,5827,2748],{"class":228},[93,5829,5830,5833,5836],{"class":95,"line":154},[93,5831,5832],{"class":228},"  console.",[93,5834,5835],{"class":1288},"log",[93,5837,5838],{"class":228},"(payload);\n",[93,5840,5841,5843,5845],{"class":95,"line":159},[93,5842,5832],{"class":228},[93,5844,5835],{"class":1288},[93,5846,5847],{"class":228},"(\n",[93,5849,5850,5853],{"class":95,"line":164},[93,5851,5852],{"class":241},"    \"[firebase-messaging-sw.js] Received background message \"",[93,5854,259],{"class":228},[93,5856,5857],{"class":95,"line":170},[93,5858,5859],{"class":228},"    payload\n",[93,5861,5862],{"class":95,"line":176},[93,5863,5864],{"class":228},"  );\n",[93,5866,5867],{"class":95,"line":1622},[93,5868,5869],{"class":1282},"  // Customize notification here\n",[93,5871,5872,5874,5877,5879,5882],{"class":95,"line":1630},[93,5873,2572],{"class":1446},[93,5875,5876],{"class":234}," notificationTitle",[93,5878,2578],{"class":1446},[93,5880,5881],{"class":241}," \"Background Message Title\"",[93,5883,5732],{"class":228},[93,5885,5886,5888,5891,5893],{"class":95,"line":1638},[93,5887,2572],{"class":1446},[93,5889,5890],{"class":234}," notificationOptions",[93,5892,2578],{"class":1446},[93,5894,2748],{"class":228},[93,5896,5897,5900,5903],{"class":95,"line":1646},[93,5898,5899],{"class":228},"    body: ",[93,5901,5902],{"class":241},"\"Background Message body.\"",[93,5904,259],{"class":228},[93,5906,5907,5910],{"class":95,"line":1654},[93,5908,5909],{"class":228},"    icon: ",[93,5911,5912],{"class":241},"\"/vite.svg\"\n",[93,5914,5915],{"class":95,"line":1662},[93,5916,5917],{"class":228},"  };\n",[93,5919,5920],{"class":95,"line":1670},[93,5921,118],{"emptyLinePlaceholder":117},[93,5923,5924,5927,5930],{"class":95,"line":1678},[93,5925,5926],{"class":228},"  self.registration.",[93,5928,5929],{"class":1288},"showNotification",[93,5931,5932],{"class":228},"(notificationTitle, notificationOptions);\n",[93,5934,5935],{"class":95,"line":1686},[93,5936,5937],{"class":228},"});\n",[10,5939,5940],{},"To register the service worker you just need to do this",[84,5942,5944],{"className":2397,"code":5943,"language":2399,"meta":89,"style":89},"const registration = await navigator.serviceWorker.register(\n  \"./firebase-messaging-sw.js\",\n  {\n    type: \"module\"\n  }\n);\n",[50,5945,5946,5965,5972,5977,5985,5989],{"__ignoreMap":89},[93,5947,5948,5950,5953,5955,5957,5960,5963],{"class":95,"line":96},[93,5949,5464],{"class":1446},[93,5951,5952],{"class":234}," registration",[93,5954,2578],{"class":1446},[93,5956,2581],{"class":1446},[93,5958,5959],{"class":228}," navigator.serviceWorker.",[93,5961,5962],{"class":1288},"register",[93,5964,5847],{"class":228},[93,5966,5967,5970],{"class":95,"line":102},[93,5968,5969],{"class":241},"  \"./firebase-messaging-sw.js\"",[93,5971,259],{"class":228},[93,5973,5974],{"class":95,"line":108},[93,5975,5976],{"class":228},"  {\n",[93,5978,5979,5982],{"class":95,"line":114},[93,5980,5981],{"class":228},"    type: ",[93,5983,5984],{"class":241},"\"module\"\n",[93,5986,5987],{"class":95,"line":121},[93,5988,2504],{"class":228},[93,5990,5991],{"class":95,"line":127},[93,5992,2593],{"class":228},[10,5994,5995,5996,5999,6000,6003,6004],{},"Make sure you keep track of the ",[2918,5997,5998],{},"registration"," as this needs to be used when calling the ",[2918,6001,6002],{},"getToken"," method. I'm using a class for all the firebase interaction ",[1142,6005,6008],{"href":6006,"rel":6007},"https://github.com/andreitere/firebase-web-notifications/blob/main/notifications.js",[1329],"notifications.js",[1264,6010,6012],{"id":6011},"building-for-production","Building for production",[10,6014,6015,6016,6019],{},"The service worker we currently have in the root folder ",[50,6017,6018],{},"firebase-messaging-sw.js"," is not imported anywhere so vite will ignore it on build.",[10,6021,6022],{},"What we need to do it a custom build config for it so that it will get outputed in the dist folder.",[10,6024,6025,6026],{},"Quite straightforward I would say. In vite.config.js I'm using a custom config based on the VITE_CONFIG so that first i'm building the ",[50,6027,6018],{},[84,6029,6031],{"className":2397,"code":6030,"language":2399,"meta":89,"style":89},"import { defineConfig } from \"vite\";\n\nconst configs = {\n  sw: {\n    build: {\n      outdir: \"./dist\",\n      lib: {\n        entry: resolve(\"./firebase-messaging-sw.js\"),\n        fileName: \"firebase-messaging-sw\",\n        formats: [\"es\"]\n      },\n      emptyOutDir: false\n    }\n  },\n  main: {}\n};\n\nconst config = configs[process.env.VITE_CONFIG];\nexport default defineConfig({\n  ...(config || {})\n});\n",[50,6032,6033,6046,6050,6061,6066,6071,6081,6086,6101,6111,6121,6126,6133,6137,6141,6146,6150,6154,6172,6182,6196],{"__ignoreMap":89},[93,6034,6035,6037,6039,6041,6044],{"class":95,"line":96},[93,6036,2406],{"class":1446},[93,6038,2409],{"class":228},[93,6040,2412],{"class":1446},[93,6042,6043],{"class":241}," \"vite\"",[93,6045,5732],{"class":228},[93,6047,6048],{"class":95,"line":102},[93,6049,118],{"emptyLinePlaceholder":117},[93,6051,6052,6054,6057,6059],{"class":95,"line":108},[93,6053,5464],{"class":1446},[93,6055,6056],{"class":234}," configs",[93,6058,2578],{"class":1446},[93,6060,2748],{"class":228},[93,6062,6063],{"class":95,"line":114},[93,6064,6065],{"class":228},"  sw: {\n",[93,6067,6068],{"class":95,"line":121},[93,6069,6070],{"class":228},"    build: {\n",[93,6072,6073,6076,6079],{"class":95,"line":127},[93,6074,6075],{"class":228},"      outdir: ",[93,6077,6078],{"class":241},"\"./dist\"",[93,6080,259],{"class":228},[93,6082,6083],{"class":95,"line":133},[93,6084,6085],{"class":228},"      lib: {\n",[93,6087,6088,6091,6094,6096,6099],{"class":95,"line":138},[93,6089,6090],{"class":228},"        entry: ",[93,6092,6093],{"class":1288},"resolve",[93,6095,2587],{"class":228},[93,6097,6098],{"class":241},"\"./firebase-messaging-sw.js\"",[93,6100,2726],{"class":228},[93,6102,6103,6106,6109],{"class":95,"line":143},[93,6104,6105],{"class":228},"        fileName: ",[93,6107,6108],{"class":241},"\"firebase-messaging-sw\"",[93,6110,259],{"class":228},[93,6112,6113,6116,6119],{"class":95,"line":149},[93,6114,6115],{"class":228},"        formats: [",[93,6117,6118],{"class":241},"\"es\"",[93,6120,1781],{"class":228},[93,6122,6123],{"class":95,"line":154},[93,6124,6125],{"class":228},"      },\n",[93,6127,6128,6131],{"class":95,"line":159},[93,6129,6130],{"class":228},"      emptyOutDir: ",[93,6132,4007],{"class":234},[93,6134,6135],{"class":95,"line":164},[93,6136,1393],{"class":228},[93,6138,6139],{"class":95,"line":170},[93,6140,4241],{"class":228},[93,6142,6143],{"class":95,"line":176},[93,6144,6145],{"class":228},"  main: {}\n",[93,6147,6148],{"class":95,"line":1622},[93,6149,5620],{"class":228},[93,6151,6152],{"class":95,"line":1630},[93,6153,118],{"emptyLinePlaceholder":117},[93,6155,6156,6158,6161,6163,6166,6169],{"class":95,"line":1638},[93,6157,5464],{"class":1446},[93,6159,6160],{"class":234}," config",[93,6162,2578],{"class":1446},[93,6164,6165],{"class":228}," configs[process.env.",[93,6167,6168],{"class":234},"VITE_CONFIG",[93,6170,6171],{"class":228},"];\n",[93,6173,6174,6176,6178,6180],{"class":95,"line":1646},[93,6175,2436],{"class":1446},[93,6177,2439],{"class":1446},[93,6179,2442],{"class":1288},[93,6181,2445],{"class":228},[93,6183,6184,6187,6190,6193],{"class":95,"line":1654},[93,6185,6186],{"class":1446},"  ...",[93,6188,6189],{"class":228},"(config ",[93,6191,6192],{"class":1446},"||",[93,6194,6195],{"class":228}," {})\n",[93,6197,6198],{"class":95,"line":1662},[93,6199,5937],{"class":228},[10,6201,6202,6203],{},"and the build commands from ",[50,6204,6205],{},"package.json",[84,6207,6209],{"className":219,"code":6208,"language":221,"meta":89,"style":89},"{\n  \"build\": \"VITE_CONFIG=main vite build && VITE_CONFIG=sw vite build\",\n  \"build:sw\": \"VITE_CONFIG=sw vite build\",\n  \"build:site\": \"VITE_CONFIG=main vite build\"\n}\n",[50,6210,6211,6215,6227,6239,6249],{"__ignoreMap":89},[93,6212,6213],{"class":95,"line":96},[93,6214,229],{"class":228},[93,6216,6217,6220,6222,6225],{"class":95,"line":102},[93,6218,6219],{"class":234},"  \"build\"",[93,6221,253],{"class":228},[93,6223,6224],{"class":241},"\"VITE_CONFIG=main vite build && VITE_CONFIG=sw vite build\"",[93,6226,259],{"class":228},[93,6228,6229,6232,6234,6237],{"class":95,"line":108},[93,6230,6231],{"class":234},"  \"build:sw\"",[93,6233,253],{"class":228},[93,6235,6236],{"class":241},"\"VITE_CONFIG=sw vite build\"",[93,6238,259],{"class":228},[93,6240,6241,6244,6246],{"class":95,"line":114},[93,6242,6243],{"class":234},"  \"build:site\"",[93,6245,253],{"class":228},[93,6247,6248],{"class":241},"\"VITE_CONFIG=main vite build\"\n",[93,6250,6251],{"class":95,"line":121},[93,6252,111],{"class":228},[10,6254,6255,6256,6259],{},"in the end, running ",[50,6257,6258],{},"pnpm run build"," will output all the required files.",[1264,6261,6263],{"id":6262},"sending-the-notification","Sending the notification",[10,6265,6266],{},"If when navigating to the main page won't display the token click on subscribe. You will be requested for Notifications permissions. Pay attention in case the browser is dening them by default. You will have to reset that.",[10,6268,6269,6270,6273],{},"Grab the token, go to firebase console -> your project -> Messaging and compose a notification. Click on ",[50,6271,6272],{},"send test message"," and use the token you got after subscribing.",[455,6275,6276],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#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 .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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":89,"searchDepth":102,"depth":102,"links":6278},[6279,6280,6281],{"id":5415,"depth":102,"text":5416},{"id":5643,"depth":102,"text":5644},{"id":5672,"depth":102,"text":5673,"children":6282},[6283,6284,6285],{"id":5676,"depth":108,"text":5677},{"id":6011,"depth":108,"text":6012},{"id":6262,"depth":108,"text":6263},"2023-02-05","A comprehensive guide to implementing Firebase Cloud Messaging (FCM) in a ViteJS application, including production-ready setup, service worker configuration, and practical code examples.",{"totalTime":6289,"tool":6290,"steps":6295},"PT45M",[6291,6292,6293,6294],"Node.js","pnpm or npm","Firebase account","Code editor",[6296,6299,6302,6305,6308,6311,6314],{"name":6297,"text":6298},"Create Firebase project","Go to Firebase Console and create a new project, then register your web app to get the firebaseConfig credentials.",{"name":6300,"text":6301},"Configure environment variables","Create a .env file with all Firebase credentials including API key, project ID, messaging sender ID, app ID, and VAPKEY from Web Push Certificates.",{"name":6303,"text":6304},"Set up Firebase config file","Create firebase.config.js that imports credentials from environment variables using import.meta.env.",{"name":6306,"text":6307},"Create the service worker","Create firebase-messaging-sw.js with Firebase messaging initialization and background message handler, keeping it in src folder for Vite processing.",{"name":6309,"text":6310},"Register the service worker","Manually register the service worker with type module option to enable ES module imports and proper Vite compilation.",{"name":6312,"text":6313},"Configure Vite build","Add custom Vite config to build the service worker separately, ensuring it outputs to the dist folder with proper processing.",{"name":6315,"text":6316},"Test notifications","Run the app, click Subscribe to get the FCM token, then send a test notification from Firebase Console using that token.",{"external":468,"lastmod":2298},"/blog/firebase-web-notifications-tutorial-2023",{"title":5390,"description":6287},"blog/4.firebase-web-notifications-tutorial-2023",[6322,6323,6324,6325,4811,6326],"firebase","fcm","notifications","web","vitejs","hd3T8I-SgMUzxjNibLLKl-RU8sy_NDF5ty83egH8Zvc",{"id":6329,"title":6330,"body":6331,"category":4799,"date":6791,"description":6792,"draft":468,"extension":469,"howto":490,"image":490,"meta":6793,"navigation":117,"path":6794,"seo":6795,"stem":6796,"tags":6797,"__hash__":6800},"blog/blog/7.vue3-vuex-4-loading-wait.md","Vuex4 Module to track your loading flags",{"type":7,"value":6332,"toc":6788},[6333,6345,6348,6361,6366,6369,6372,6518,6521,6607,6610,6653,6656,6718,6721,6756,6759,6762,6764,6766,6769,6785],[446,6334,6335,6339],{},[5396,6336,6338],{"id":6337},"repository","Repository",[10,6340,6341],{},[1142,6342,6343],{"href":6343,"rel":6344},"https://github.com/andreitere/vue3-wait-example",[1329],[10,6346,6347],{},"Whenever you build a frontend application which calls backend apis and you also need to display the loading state you end up with multiple flags\nin your code to manage this.",[10,6349,6350,6351,6354,6355,6360],{},"For Vue there is already an ",[45,6352,6353],{},"awesome"," module ( ",[1142,6356,6359],{"href":6357,"rel":6358},"https://github.com/f/vue-wait",[1329],"vue-wait"," ) which makes this easier but I wanted something quite simple with no additional dependency.",[10,6362,6363,6364,339],{},"If it's not clear this is highly insipired by ",[2918,6365,6359],{},[10,6367,6368],{},"To keep it straight forward:",[10,6370,6371],{},"wait module:",[84,6373,6375],{"className":2397,"code":6374,"language":2399,"meta":89,"style":89},"export const wait = {\n    namespaced: true,\n    state() {\n        return {\n            queue: []\n        }\n    },\n    mutations: {\n        start(state, task) {\n            state.queue.push(task)\n        },\n        end(state, task) {\n            state.queue = state.queue.filter(item => item !== task);\n        }\n    }\n}\n",[50,6376,6377,6390,6399,6406,6413,6418,6422,6426,6431,6448,6459,6463,6478,6506,6510,6514],{"__ignoreMap":89},[93,6378,6379,6381,6383,6386,6388],{"class":95,"line":96},[93,6380,2436],{"class":1446},[93,6382,4111],{"class":1446},[93,6384,6385],{"class":234}," wait",[93,6387,2578],{"class":1446},[93,6389,2748],{"class":228},[93,6391,6392,6395,6397],{"class":95,"line":102},[93,6393,6394],{"class":228},"    namespaced: ",[93,6396,2673],{"class":234},[93,6398,259],{"class":228},[93,6400,6401,6404],{"class":95,"line":108},[93,6402,6403],{"class":1288},"    state",[93,6405,2567],{"class":228},[93,6407,6408,6411],{"class":95,"line":114},[93,6409,6410],{"class":1446},"        return",[93,6412,2748],{"class":228},[93,6414,6415],{"class":95,"line":121},[93,6416,6417],{"class":228},"            queue: []\n",[93,6419,6420],{"class":95,"line":127},[93,6421,1388],{"class":228},[93,6423,6424],{"class":95,"line":133},[93,6425,4186],{"class":228},[93,6427,6428],{"class":95,"line":138},[93,6429,6430],{"class":228},"    mutations: {\n",[93,6432,6433,6436,6438,6441,6443,6446],{"class":95,"line":143},[93,6434,6435],{"class":1288},"        start",[93,6437,2587],{"class":228},[93,6439,6440],{"class":2686},"state",[93,6442,2720],{"class":228},[93,6444,6445],{"class":2686},"task",[93,6447,4158],{"class":228},[93,6449,6450,6453,6456],{"class":95,"line":149},[93,6451,6452],{"class":228},"            state.queue.",[93,6454,6455],{"class":1288},"push",[93,6457,6458],{"class":228},"(task)\n",[93,6460,6461],{"class":95,"line":154},[93,6462,5049],{"class":228},[93,6464,6465,6468,6470,6472,6474,6476],{"class":95,"line":159},[93,6466,6467],{"class":1288},"        end",[93,6469,2587],{"class":228},[93,6471,6440],{"class":2686},[93,6473,2720],{"class":228},[93,6475,6445],{"class":2686},[93,6477,4158],{"class":228},[93,6479,6480,6483,6485,6488,6490,6492,6495,6497,6500,6503],{"class":95,"line":164},[93,6481,6482],{"class":228},"            state.queue ",[93,6484,4169],{"class":1446},[93,6486,6487],{"class":228}," state.queue.",[93,6489,4215],{"class":1288},[93,6491,2587],{"class":228},[93,6493,6494],{"class":2686},"item",[93,6496,4223],{"class":1446},[93,6498,6499],{"class":228}," item ",[93,6501,6502],{"class":1446},"!==",[93,6504,6505],{"class":228}," task);\n",[93,6507,6508],{"class":95,"line":170},[93,6509,1388],{"class":228},[93,6511,6512],{"class":95,"line":176},[93,6513,1393],{"class":228},[93,6515,6516],{"class":95,"line":1622},[93,6517,111],{"class":228},[10,6519,6520],{},"how it mutation can be called from other modules:",[84,6522,6524],{"className":2397,"code":6523,"language":2399,"meta":89,"style":89}," async loadData({commit}) {\n    commit(\"wait/start\", \"load_data\", { root: true });\n    setTimeout(() => {\n        commit(\"wait/end\", \"load_data\", { root: true });\n    }, 2000);\n},\n",[50,6525,6526,6537,6560,6572,6592,6602],{"__ignoreMap":89},[93,6527,6528,6531,6534],{"class":95,"line":96},[93,6529,6530],{"class":228}," async ",[93,6532,6533],{"class":1288},"loadData",[93,6535,6536],{"class":228},"({commit}) {\n",[93,6538,6539,6542,6544,6547,6549,6552,6555,6557],{"class":95,"line":102},[93,6540,6541],{"class":1288},"    commit",[93,6543,2587],{"class":228},[93,6545,6546],{"class":241},"\"wait/start\"",[93,6548,2720],{"class":228},[93,6550,6551],{"class":241},"\"load_data\"",[93,6553,6554],{"class":228},", { root: ",[93,6556,2673],{"class":234},[93,6558,6559],{"class":228}," });\n",[93,6561,6562,6565,6568,6570],{"class":95,"line":108},[93,6563,6564],{"class":1288},"    setTimeout",[93,6566,6567],{"class":228},"(() ",[93,6569,2693],{"class":1446},[93,6571,2748],{"class":228},[93,6573,6574,6577,6579,6582,6584,6586,6588,6590],{"class":95,"line":114},[93,6575,6576],{"class":1288},"        commit",[93,6578,2587],{"class":228},[93,6580,6581],{"class":241},"\"wait/end\"",[93,6583,2720],{"class":228},[93,6585,6551],{"class":241},[93,6587,6554],{"class":228},[93,6589,2673],{"class":234},[93,6591,6559],{"class":228},[93,6593,6594,6597,6600],{"class":95,"line":121},[93,6595,6596],{"class":228},"    }, ",[93,6598,6599],{"class":234},"2000",[93,6601,2593],{"class":228},[93,6603,6604],{"class":95,"line":127},[93,6605,6606],{"class":228},"},\n",[10,6608,6609],{},"To get the data in your components:",[84,6611,6613],{"className":2397,"code":6612,"language":2399,"meta":89,"style":89},"...\nconst isLoading = computed(() => store.state.wait.queue.includes(\"load_data\"));\n...\n",[50,6614,6615,6620,6649],{"__ignoreMap":89},[93,6616,6617],{"class":95,"line":96},[93,6618,6619],{"class":1446},"...\n",[93,6621,6622,6624,6627,6629,6632,6634,6636,6639,6642,6644,6646],{"class":95,"line":102},[93,6623,5464],{"class":1446},[93,6625,6626],{"class":234}," isLoading",[93,6628,2578],{"class":1446},[93,6630,6631],{"class":1288}," computed",[93,6633,6567],{"class":228},[93,6635,2693],{"class":1446},[93,6637,6638],{"class":228}," store.state.wait.queue.",[93,6640,6641],{"class":1288},"includes",[93,6643,2587],{"class":228},[93,6645,6551],{"class":241},[93,6647,6648],{"class":228},"));\n",[93,6650,6651],{"class":95,"line":108},[93,6652,6619],{"class":1446},[10,6654,6655],{},"... or we can have our custom utility function:",[84,6657,6659],{"className":2397,"code":6658,"language":2399,"meta":89,"style":89},"export const useWait = (task) => {\n    const store = useStore();\n    return computed(() => store.state.wait.queue.includes(task));\n};\n",[50,6660,6661,6683,6697,6714],{"__ignoreMap":89},[93,6662,6663,6665,6667,6670,6672,6675,6677,6679,6681],{"class":95,"line":96},[93,6664,2436],{"class":1446},[93,6666,4111],{"class":1446},[93,6668,6669],{"class":1288}," useWait",[93,6671,2578],{"class":1446},[93,6673,6674],{"class":228}," (",[93,6676,6445],{"class":2686},[93,6678,2690],{"class":228},[93,6680,2693],{"class":1446},[93,6682,2748],{"class":228},[93,6684,6685,6687,6690,6692,6695],{"class":95,"line":102},[93,6686,4635],{"class":1446},[93,6688,6689],{"class":234}," store",[93,6691,2578],{"class":1446},[93,6693,6694],{"class":1288}," useStore",[93,6696,2606],{"class":228},[93,6698,6699,6701,6703,6705,6707,6709,6711],{"class":95,"line":108},[93,6700,4740],{"class":1446},[93,6702,6631],{"class":1288},[93,6704,6567],{"class":228},[93,6706,2693],{"class":1446},[93,6708,6638],{"class":228},[93,6710,6641],{"class":1288},[93,6712,6713],{"class":228},"(task));\n",[93,6715,6716],{"class":95,"line":114},[93,6717,5620],{"class":228},[10,6719,6720],{},"and use it like this:",[84,6722,6724],{"className":2397,"code":6723,"language":2399,"meta":89,"style":89},"import {useWait} from './store/wait';\nconst isLoading = useWait(\"load_data\");\n",[50,6725,6726,6740],{"__ignoreMap":89},[93,6727,6728,6730,6733,6735,6738],{"class":95,"line":96},[93,6729,2406],{"class":1446},[93,6731,6732],{"class":228}," {useWait} ",[93,6734,2412],{"class":1446},[93,6736,6737],{"class":241}," './store/wait'",[93,6739,5732],{"class":228},[93,6741,6742,6744,6746,6748,6750,6752,6754],{"class":95,"line":102},[93,6743,5464],{"class":1446},[93,6745,6626],{"class":234},[93,6747,2578],{"class":1446},[93,6749,6669],{"class":1288},[93,6751,2587],{"class":228},[93,6753,6551],{"class":241},[93,6755,2593],{"class":228},[10,6757,6758],{},"That's it.",[10,6760,6761],{},"Thank you for reading 😄",[30,6763],{},[33,6765,4765],{"id":4764},[10,6767,6768],{},"For modern Vue 3 development patterns:",[17,6770,6771,6778],{},[20,6772,6773,6777],{},[45,6774,6775],{},[1142,6776,2877],{"href":2876}," - Migration path from Vuex to the new Vue 3 state management standard",[20,6779,6780,6784],{},[45,6781,6782],{},[1142,6783,2868],{"href":2867}," - Complex state management scenarios with external services",[455,6786,6787],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}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 .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":89,"searchDepth":102,"depth":102,"links":6789},[6790],{"id":4764,"depth":102,"text":4765},"2021-08-05","A lightweight Vuex module for Vue 3 that elegantly manages loading states across your application, with practical examples and zero dependencies.",{"external":468},"/blog/vue3-vuex-4-loading-wait",{"title":6330,"description":6792},"blog/7.vue3-vuex-4-loading-wait",[4799,6798,2901,6799],"vuex","module","k2V2FVGttyeDZdUqXUrRzvUe65TlpEwp5uHsDm-j6Qo",{"id":6802,"title":6803,"body":6804,"category":2893,"date":6837,"description":6838,"draft":468,"extension":469,"howto":490,"image":490,"meta":6839,"navigation":117,"path":6840,"seo":6841,"stem":6842,"tags":6843,"__hash__":6848},"blog/blog/2.facebook-login-android-key.md","Facebook Login on Android - What the key!?",{"type":7,"value":6805,"toc":6835},[6806,6809,6812,6819,6828,6831,6833],[10,6807,6808],{},"If you end up here after endless debugging, not knowing why your Facebook login still doesn't work after you put all the key hashes you could find in that Facebook app dashboard... I feel you. :(",[10,6810,6811],{},"Been there done that.",[10,6813,6814,6815,6818],{},"Two painful days trying to debug and see why the keys I'm getting from the ",[2918,6816,6817],{},"keytool"," export are not a match for the app version in the store.",[10,6820,6821,6822,6827],{},"Either a total brainslip or just got over this without acknowledging it but at some point you can opt-in for ",[1142,6823,6826],{"href":6824,"rel":6825},"https://developer.android.com/studio/publish/app-signing",[1329],"Play App Signing"," which takes care of signing your app. If you did this the key needed to be put in the facebook app dashboard needs to be taken from Google Play Console, not from your local key store.",[10,6829,6830],{},"Go to your app page in the console, search for App Signing and there you'll find the certificates you'll need to convert to base64 and put in the Facebook app dashboard.",[10,6832,6758],{},[10,6834,6761],{},{"title":89,"searchDepth":102,"depth":102,"links":6836},[],"2021-06-05","A practical guide to fixing Facebook Login issues on Android when using Play App Signing, including how to correctly generate and configure key hashes.",{"external":468},"/blog/facebook-login-android-key",{"title":6803,"description":6838},"blog/2.facebook-login-android-key",[6844,6845,6846,6847],"android","login","facebook","app signing","P5aFbhrPO022kRmm5A8oyrlJjqh34M1igI0VW2_wznI",{"id":6850,"title":6851,"body":6852,"category":6876,"date":6877,"description":6878,"draft":468,"extension":469,"howto":490,"image":490,"meta":6879,"navigation":117,"path":6880,"seo":6881,"stem":6882,"tags":490,"__hash__":6883},"blog/blog/1.hello-world.md","Hello World",{"type":7,"value":6853,"toc":6874},[6854,6857,6860,6863,6866,6869,6872],[10,6855,6856],{},"Ok. Starting this again.",[10,6858,6859],{},"While for the past years I maintained a blog I didn't always had the time, motivation or content ideas to properly populate it.",[10,6861,6862],{},"Lately, this changed a bit. I've been gathering a lot of experience, met a lot\nof challenges and I want to share these with you.",[10,6864,6865],{},"I want to share quick tips, scripts, tricks and solutions to any problem I may\nencounter. It probably won't be super detailed as I don't have so much time but I'll try to make it useful.",[10,6867,6868],{},"Also, i'm still working on this website, so there may be some things which are not working as expected or not working at all. Bear with me.",[10,6870,6871],{},"That's it 😌. I hope you'll enjoy it.",[10,6873,5343],{},{"title":89,"searchDepth":102,"depth":102,"links":6875},[],"random","2021-06-01","Welcome to my personal blog where I share practical tips, development insights, and real-world solutions from my tech journey.",{"external":468},"/blog/hello-world",{"title":6851,"description":6878},"blog/1.hello-world","D_Dxq5Dscnyzj9PIFBcZtFtoakcINtMOrPKcPZJjVuc",[],1793650645931]