1"use strict";(self.webpackChunkcaprover_docs_site=self.webpackChunkcaprover_docs_site||[]).push([["4719"],{4681(e,n,r){r.r(n),r.d(n,{metadata:()=>t,default:()=>d,frontMatter:()=>a,contentTitle:()=>l,toc:()=>c,assets:()=>i});var t=JSON.parse('{"id":"stateless-with-persistent-data","title":"Stateless with Persistent data","description":"Before you start here, please read:","source":"@site/../content/en/docs/stateless-with-persistent-data.md","sourceDirName":".","slug":"/stateless-with-persistent-data","permalink":"/docs/stateless-with-persistent-data","draft":false,"unlisted":false,"editUrl":"https://github.com/caprover/caprover-website/edit/master/content/en/docs/stateless-with-persistent-data.md","tags":[],"version":"current","frontMatter":{"id":"stateless-with-persistent-data","title":"Stateless with Persistent data","sidebar_label":"Stateless with Persistent data"},"sidebar":"docs","previous":{"title":"Static React App","permalink":"/docs/recipe-deploy-create-react-app"},"next":{"title":"Docker Compose","permalink":"/docs/docker-compose"}}'),s=r(4848),o=r(8453);let a={id:"stateless-with-persistent-data",title:"Stateless with Persistent data",sidebar_label:"Stateless with Persistent data"},l,i={},c=[{value:"Placeholder variables",id:"placeholder-variables",level:4},{value:"1) Preparing rclone",id:"1-preparing-rclone",level:3},{value:"2) Prepare your storage system",id:"2-prepare-your-storage-system",level:3},{value:"3) Preparing the docker rclone plugin",id:"3-preparing-the-docker-rclone-plugin",level:3},{value:"4) Preparing the app",id:"4-preparing-the-app",level:3}];function h(e){let n={a:"a",code:"code",em:"em",h3:"h3",h4:"h4",hr:"hr",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Before you start here, please read:"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/persistent-apps",children:"Persistent Apps"})}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:['This documentation will help you set-up a stateless app with persistant data. For example a website hosted with "',(0,s.jsx)(n.strong,{children:"php:7.4-apache"}),'" serving the "',(0,s.jsx)(n.strong,{children:"uploads"}),'" ( ',(0,s.jsx)(n.code,{children:"/var/www/html/uploads"})," ) folder or any other folder you define from for example AWS or Wasabi S3, or any of the other ",(0,s.jsx)(n.a,{href:"https://rclone.org/overview/",children:"storage systems rclone supports"}),". This makes it possible to have an otherwise pinned to node X app, to fail-over to a other node within the same Docker swarm."]}),"\n",(0,s.jsxs)(n.p,{children:["There are multiple docker volume plugins to allow this set-up, I ",(0,s.jsx)(n.a,{href:"https://caprover.slack.com/archives/DLR2Q4TC1",children:"@Dani\xebl"}),' and my colleague Floris started out with "',(0,s.jsx)(n.strong,{children:"rexray/s3fs"}),'" but switched over to "',(0,s.jsx)(n.strong,{children:"sapk/plugin-rclone"}),'" as it was more stable, and handled fail-overs from node X to Y better.']}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Important note:"})," The below steps are for intermediate and advanced (",(0,s.jsx)(n.em,{children:"linux"}),") users."]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h4,{id:"placeholder-variables",children:"Placeholder variables"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"$volumename"})," can for example be ",(0,s.jsx)(n.code,{children:"captain--yourappname-rclone"})]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"$remotename"})," can for example be ",(0,s.jsx)(n.code,{children:"captain--yourappname"})]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"$remotename/path"})," can for example be ",(0,s.jsx)(n.code,{children:"captain--yourappname/_data"})]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"$rcloneremotename"})," can for example be ",(0,s.jsx)(n.code,{children:"wasabi-s3"})]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"1-preparing-rclone",children:"1) Preparing rclone"}),"\n",(0,s.jsxs)(n.p,{children:["You start by creating the ",(0,s.jsx)(n.code,{children:"rclone.conf"})," file, this can be done on any (",(0,s.jsx)(n.em,{children:"local"}),") machine that has rclone installed.\nFor the ease of this documentation we'll assume you have ",(0,s.jsx)(n.a,{href:"https://rclone.org/install/",children:"rclone installed"})," on the primary node of your Docker swarm."]}),"\n",(0,s.jsxs)(n.p,{children:['From this primary node, run "',(0,s.jsx)(n.strong,{children:(0,s.jsx)(n.a,{href:"https://rclone.org/commands/rclone_config/",children:"rclone config"})}),'" to create the config file, when done run ',(0,s.jsx)(n.code,{children:"rclone config file"})," to know where the config file is stored.\nWhen you're using 'root' as you're user it will be stored in ",(0,s.jsx)(n.code,{children:"/root/.config/rclone/rclone.conf"})," which we will use as a reference for the further writing of this guide."]}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"rclone.conf"})," file will look like something like this:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"[$rcloneremotename]\ntype = s3\nprovider = Wasabi\naccess_key_id = YourAccessKey\nsecret_access_key = YourSecretAccessKey\nregion = eu-central-1\nendpoint = s3.eu-central-1.wasabisys.com\nenv_auth = false\nupload_cutoff = 25M\nchunk_size = 5M\ndisable_checksum = false\nupload_concurrency = 3\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Make sure each swarm node has the ",(0,s.jsx)(n.code,{children:"/root/.config/rclone/rclone.conf"})," file, with the exact same content, double check by using ",(0,s.jsx)(n.code,{children:"md5sum /root/.config/rclone/rclone.conf"})," and compare the checksums.\n",(0,s.jsx)(n.em,{children:"Or at least make sure, if there are multiple configs avaiable, that the one you'll be using is the same"})]}),"\n",(0,s.jsx)(n.h3,{id:"2-prepare-your-storage-system",children:"2) Prepare your storage system"}),"\n",(0,s.jsxs)(n.p,{children:["Make sure that your S3 bucket (or the folder you'll be using on the storage system you've configured via ",(0,s.jsx)(n.code,{children:"rclone config"}),") actually exists and that the name of the bucket / folder matches the name of ",(0,s.jsx)(n.code,{children:"$remotename"})]}),"\n",(0,s.jsx)(n.h3,{id:"3-preparing-the-docker-rclone-plugin",children:"3) Preparing the docker rclone plugin"}),"\n",(0,s.jsxs)(n.p,{children:["On each swarm node install the docker volume plugin with the help of this command ",(0,s.jsx)(n.code,{children:"docker plugin install sapk/plugin-rclone"})]}),"\n",(0,s.jsxs)(n.p,{children:['Then execute this command on each node, the below one was specially for "',(0,s.jsxs)(n.strong,{children:["php",":N",".N-apache"]}
1),'" containers ( ',(0,s.jsx)(n.em,{children:"for example php:7.4-apache"})," )"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:'docker volume create --driver sapk/plugin-rclone --opt config="$(base64 /root/.config/rclone/rclone.conf)" --opt args="--uid 33 --gid 33 --allow-root --allow-other" --opt remote=$rcloneremotename:$remotename/path --name $volumename\n'})}),"\n",(0,s.jsx)(n.p,{children:"If you have an S3 bucket, in which files get uploaded via either AWS / Wasabi web interface, or anything else such as SFTPGo mounted to the S3 bucket, then you'll need to tell rclone to refresh it's dir cache:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:'docker volume create --driver sapk/plugin-rclone --opt config="$(base64 /root/.config/rclone/rclone.conf)" --opt args="--uid 33 --gid 33 --allow-root --allow-other --dir-cache-time 5s" --opt remote=$rcloneremotename:$remotename/path --name $volumename\n'})}),"\n",(0,s.jsxs)(n.p,{children:['What happens is that "',(0,s.jsx)(n.strong,{children:(0,s.jsx)(n.a,{href:"https://rclone.org/commands/rclone_mount/",children:"rclone mount"})}),'" mounts the volume on the Docker swarm node(s), though be aware that other flags/parameters can benefit or negatively impact your app experience so test them throughout.']}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"The above UID and GID are matched to Apache2 and can differentiate with other apps."})}),"\n",(0,s.jsx)(n.h3,{id:"4-preparing-the-app",children:"4) Preparing the app"}),"\n",(0,s.jsxs)(n.p,{children:['Then deploy a blank app with "',(0,s.jsx)(n.strong,{children:"Has Persistent Data"}),"\" unchecked, and set-up it's parameters of you're liking under the \"",(0,s.jsx)(n.strong,{children:"HTTP Settings"}),'", "',(0,s.jsx)(n.strong,{children:"App Configs"}),'" & "',(0,s.jsx)(n.strong,{children:"Deployment"}),'" tabs.']}),"\n",(0,s.jsxs)(n.p,{children:['In the "',(0,s.jsx)(n.strong,{children:"App Configs"}),'" in the "',(0,s.jsx)(n.strong,{children:"Service Update Override"}),'" section, place the following.\nBe aware that ',(0,s.jsx)(n.code,{children:"/var/www/html/uploads"})," is a path / folder you should define yourself, but is used for reference here."]}),"\n",(0,s.jsxs)(n.p,{children:['Set the "',(0,s.jsx)(n.strong,{children:"ReadOnly"}),'" value to either ',(0,s.jsx)(n.code,{children:"true"})," or ",(0,s.jsx)(n.code,{children:"false"})," based on what is appropirate to you're app.\nIf you're php app allows it's users to upload files, set it to ",(0,s.jsx)(n.code,{children:"false"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:'TaskTemplate:\n ContainerSpec:\n Mounts: [\n {\n "Type": "volume",\n "Source": "$volumename",\n "Target": "/var/www/html/uploads",\n "ReadOnly": false\n }\n ]\n'})}),"\n",(0,s.jsxs)(n.p,{children:['This way the application running on "',(0,s.jsx)(n.em,{children:"php:7.4-apache"}),'" can move from node1 to any other nodes that are properly configured.']}),"\n",(0,s.jsxs)(n.p,{children:["If you have a question, or run into issues, please get in contact via Slack in the General channel and if needed mention me ",(0,s.jsx)(n.a,{href:"https://caprover.slack.com/archives/DLR2Q4TC1",children:"@Dani\xebl"})," and I or anyone else will try to help you out."]})]})}function d(e={}){let{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(h,{...e})}):h(e)}},8453(e,n,r){r.d(n,{R:()=>a,x:()=>l});var t=r(6540);let s={},o=t.createContext(s);function a(e){let n=t.useContext(o);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:a(e.components),t.createElement(o.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.