A Guide for Running Rails in Docker

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey there Nick detakas here in this video we're going to go over all sorts of different tips and best practices for running rails and Docker it's going to be an accumulation of everything I've learned from using Docker and rails for the last seven or so years now what's really interesting is as of rail 7.1 which by the way isn't now at the time of making this video yet it does have preliminary support to generate an official Docker file and a couple Associated files like a Docker ignore file and an entry point script when you run a rails new so I really like that dhh has made the decision to officially support Docker in rails but in this video we're going to be looking at this example dockerized rails app here which has a little bit more opinions Associated to it it's been something I've been using in development and production now for many many different years but uh you know this video is not going to be something like in US versus them thing where you know I'm going to try to convince you to use my project instead of the official Docker file in fact I'm actually contributing a number of different pull requests to the official rails dockerfile and project in general talk to dhh a little bit about some of the stuff and I really do like the direction it's going in and I'm absolutely going to be using those files in this example but when things get big just a little bit more but for now you know anything that you see in this video you can apply to the official Docker file and you know really what it comes down to is you'll probably end up needing to do a little bit more manual work for a lot of other things like for example you know this example app uses Docker compose but at least at the time of making this video the official rails generator doesn't support using Docker compose and that's fine you know different goals for what things are doing but yeah I really do appreciate what dhh is doing here and uh yeah that guy has some laser focus on exactly what he wants to see in the project and I think it's going to evolve over time for the better and uh yeah really excited to see where that one comes but yeah in this video we're going to be looking at a lot of code from here you know running some commands seeing how things work etc etc but it's not going to be like an end-to-end guide from dockerizing wrap from Ground Zero but you will walk away at the end of this video knowing basically all the components that may need to change to use Docker and maybe pick up a couple little tips along the way but yeah let's start a little bit about just Docker compose in general because as you know your rails app is probably not just running a web server like Puma it and that's it right your rails app you might not be using sqlite but instead you could be using postgres or MySQL so right away you need to be able to run multiple things together right like you might want to run your Puma app server as well as postgres but maybe you also have a background worker as well you know you could be using sidekick or rescue or a good job or you know some other solution where in addition to running your Puna web server you need to run your background worker as well so now we're up to potentially three things to run and then of course you're gonna probably be using redis as well either as a back end for your background worker or maybe you're using action cable or you know just want to have some caching or anything like that and you can see here too like I have a couple tags here you know you may also want to deal with running es build and potentially Tailwind as well so we're dealing with all these different processes that you would like to run and running them all by hand is a little bit tedious right and you're probably used to this if you're not using Docker you'd be using foreman right and a proc file and you can just you know run whatever the format command is it's been a long time since I ran it but it will run all these things and like Multiplex all the outputs to your terminal and you know you don't need to like manually open up like six different Terminals and run stuff and you know Docker has a very similar solution that you can use not quite similar in the sense that it's doing the same thing as forming but yeah well let's start diving through some code and we'll take a look at some stuff so if I open up this Docker compose.yaml file here and by the way I've done quite a few videos in the past around General Docker things like for example you know I've got like a 7 000 word blog post and a one hour video going over like all these little things like what is this and like you know all sorts of different things and I will do my best to link all those uh either in the description of this video or pop them up in cards but I don't want to go over all of that stuff in this video as well I want to keep this one a little bit focused specifically to rails but you know for the general stuff I will uh add some rails tidbits in this video of course as we get there but the takeaway for now right now is you know in this compose file you know we can see we're running quite a few different Services here you know we have postgres we have redis we have web which would be your Puma app server or unicorn whatever one you want to use then you've got your worker in this case this project is using sidekick but if you're using rescue or a good job or something else you can run a different command there then we have action cable running as its own dedicated service and then you also have Js and CSS which would be es build as well as Tailwind for this specific project here which by the way you know uh I'll leave a link to this as well you know it's open source MIT licensed it's an example app that you can use to basically start up your own app there's no strings attached you know I've been maintaining it for years etc etc but yeah the idea here is you know you can just run your Docker compose up here I've already built the project out of video and you know it's going to create all these different containers here you know for redis and yes build Tailwind postgres all of them and now I can just go to my browser and then just go to localhost Port 8000 because that's where I have this one running and that's it running on Rails you can see the different versions that we're using here I keep this project up to date you know every week or so I go in there update all the dependencies you can see we're running it in Dev mode etc etc and uh yeah the app is up and running now if you were using the official generated files from rails uh with a new generator with rail 7.1 you wouldn't be getting this Docker compose file by default so you would be able to basically build a Docker image and then run puma and your other stuff in there but like what about postgres you know what about redis what about uh maybe I don't know elasticsearch or something like that like what about running psychic separately like there's all these little things that you would need to do on your own and this project puts all the stuff there maybe over time the compose file gets rolled into the official generator we will see you know I don't want to keep talking about that in this video like comparing them but you know that's basically where we're at here so you know this idea of having a compose file to upload your projects is great because now on your Dev box you don't need to install postgres or MySQL or redis or whatever you can just up your project in one shot one command and you're done and uh what's really neat too is Docker compose and I've done videos about this one specific videos in the past is it supports this idea of profiles so for example and I don't want to get super deep here because that video goes into all the details but the main takeaway here is that you know in development you probably want everything to run you know you want postgres you want redis you want your web worker cable but you also want your you want your Tailwind Watcher to run so that if you change your CSS or whatever or ES build change some JavaScript you want that stuff to be incrementally updated basically in one second right so if I open up I don't know some Javascript file in this uh thing here let me see which one uh let's go into I don't know the application.js file I don't know right if I just add a console.log here log whatever save this file go back to here we can see that in 59 milliseconds the es build output here and now if I were to go to this page here and open up some Dev tools go to the console and reload we would see our DOT rate so we have incremental updates here with JavaScript and likewise what Tailwind this is all set up too like if we were to add some custom whatever you need to do classes Etc you would just get that updates but uh yeah that's really nice to have in development but in production you know you're probably not going to be running an es build Watcher or Tailwind Watcher so what Docker could post profiles lets you do is you know this comment a little bit hints at that is you know in production you just wouldn't set assets here and then you know the JS container and CSS containers wouldn't run instead you would do your you know rails pre-compile of your assets in production mode that's going to create the bundles for you and then you just serve them from a web server like nginx you know maybe put a CDN in front of that if you want or you know drop them onto S3 Etc but you know you're not going to be running those in uh production you know those containers likewise you might be running a hosted version of your database somewhere on the cloud right RDS with AWS maybe digital oceans manage Reddit server like yeah you can just choose to not put those in here if you don't want to and then yeah your application you would just configure things to connect to a specific URL for example this example application you know there's a redis URL in here somewhere which by default will just connect to redis running in a container but you can always just change the single redis URL to be whatever it needs to be to connect to a managed redis server and are good to go likewise with the database so we'll get into more patterns around the database.yaml file later but I just want to let you know that yeah that's that's that for just dealing but running multiple different containers and Dev and proud because I really love this idea of just having a single Docker composed at yaml file and we're going to also go into more details about this file later in a little bit but one main takeaway here is like yeah Dev Pride parity right I can have this one composed yaml file it's going to work in Dev it's going to work in CI it's going to run in you know work in prod or staging server before that if you wanted to and you know there's not multiple files on I need to just you know keep in sync or something like that but now let's go over uh a run script which is another video I've done in the past a little bit this is a pretty big shell script I don't want to go over it line by line but it's really interesting that you know when you're running some stuff in Docker it's a little bit annoying to have to run a whole bunch of different commands because if things are running a Docker and you know you'd have to do something like a Docker compose exact web and then maybe bash if you wanted to connect to the Shell in your running container where you know since this is about rails let's you know do a rails console we can do that too but think about it right you know Ruby is all about developer happiness and rails has a lot of really good abstractions to tuck away complexity in my opinion in all the right spots right makes it easy for you to use things and you don't need to really think about things and uh running this like sucks doesn't it like compared to just running rails console and uh you know there's other different flags that we could run and it gets like super tedious so I created this thing called a run script which is basically just a shell script where now I can do just run a rail C or rails console and is this going to do the same thing and I don't need to run the docker compose exec web all this other stuff like no the Run script the shell script over here has a couple different shell functions all set up and ready to go for me that allow me to create commands like this rails function here which automatically gets exposed as a command details are in that video that I'll link to somewhere but yeah the idea here is I can just create functions in this file and then they're exposed as commands and if I go to here kill this out and just run run you can see all the different commands that we can run and yeah it can connect right to a shell I didn't name it bash because who knows what the internal shell implementation details are you just want to connect to the Shell right it doesn't matter if it's bash or like literally like posix compliant shell and I you can connect to P SQL if you want to right you can just do run P SQL I can type there we go and now we're into P SQL and we can do you know whatever database lookups that you want to do if you want to connect to it there's one for the red CLI but yeah it's nice it's just a you know a wrapper on a couple of different command it's like this underscore DC function we can see underscored functions they're not listed in The Help menu but it just runs a Docker compose and then you know potentially exec unless you override that then there's all sorts of different TTY Shenanigans happening here because and this is other you know things that you just find out eventually right so if you run your application like this like we're doing you know this Docker uh compose exec web rails console whatever like we're opening up an interactive terminal here you know I can just continue typing into this I can do whatever I want like you know the process doesn't immediately exit that's because Docker composed by default is allocating to TTY you know some type of terminal I don't want to get into the Great Deals details about that because that's a 20 minute video in itself but um if you're running your application in something like a CI environment TTY is not going to be available so you need to compose you need to configure compose not to do that and compose has this one flag called uh Dash capital T so actually if we go to The Help menu for the exact command here we can see no TTY so in CI environments when a TTY isn't available here then we can tell composed not to allocate it by setting this flag so that's all this does here and this little condition just checks which some shell scripting or bash specifically we'll check to see if TTY is available and if it's not available then we set the flag and then the flag is set for all the compose commands you know we're basically building up our own little abstractions here to very easily you know control things in a manageable way like the setting is only defined once but all these other commands like they start referencing them anyways yeah like this pattern is super helpful you know this shell script in itself is basically like an alias file but it's a little bit more flexible a little bit more you know on steroids maybe because you can do anything that you want uh shell scripting wise now yeah let's talk a little bit um about because I was going around here seeing all sorts of different things yeah let's talk a little bit about just running like es build and talwind and developments because you know chances are we want certain things to be a little bit different here so at a convention here you know you go to your package at Json you can uh you can have these little scripts being run for build for your JavaScript and CSS for you know you don't need to be using tl1 you could be using something else but in my case I'm using Tailwind here but you can see that these scripts they're actually just running those run scripts and uh yeah if we go back to Those runs skips over here you know there's the one for ES build and there's the one for tailwind and all we do here really is just check you know are we running rails and node in production or development because when it comes to es build for running it in production mode we actually want to Minify our assets and um yeah we want to also potentially in development set the source map Etc and then we can just run our normal es build command now it's been a really long time since I looked at the generator for rails I think like these type of longer commands are probably inlined in that package.json file but you know I wanted to do slightly different things here for depending on if I'm running in Dev versus product like for example with tail end right in production yeah I want to I want to have it minified but in development I actually want the Watcher to be be there and then yeah these arguments just get passed in depending on what these environments are because in this case remember like this run script is literally being executed in the context of the container and uh yeah there's environment variables set in the EMV file for rails and node like right now they're set to development because you know I can wait here on my Dev blocks to do this but in production I would just set them to proud and yeah if conditions could be used in a shell script to do that type of thing now let's talk a little bit about running tests so I've got a test function over here and what's really interesting about this one is you know since we did a Docker compose up you know all of our containers are running and by the way like exact allows you to run commands in an existing container you know in case you're brand new to Docker but uh yeah since the up command was run and the rails EnV was set to development we can't just run our test Suite without explicitly setting that we want our test to run with rails EnV test so what this function here is doing it allows me to basically just you know end up running run test and this will run run the whole test Suite here you can see it runs pretty quick you know one second basically done and done and what that ends up doing is it actually you know this underscore DC is short for Docker compose so you know it's doing basically a Docker compose exec and then we are specifically saying when we exactly into this container and run the command let's make sure that we override the rails EnV to be set to test now in this case it's running it in the JS container instead of the web container and uh the reason for that one is and and this command by the way like this one will just default to using web because most of the commands will be run from the web container that's basically the Puma or the app server whatever you know your rails environment but uh in this case we need to have some assets available once in a while so we run these tests inside of our JS container and we're going to go into the docker file how all this stuff ends up being set up we have a very optimized Docker file using multi-stage builds Debbie and slim blah blah yeah well all it does here right exactly the container with the test environment and it runs whatever test command that we decide to pass in which by default if you don't customize that it is just going to run a rails test here so yeah you know going back to just abstractions here we can do Docker compose exec JS and then we can do rails test here and that should run something very similar to what we just saw except we only have to run and run test and by the way I'm running this without the dot slash because in my literal aliases file for my shell I have this set up already so what is that one command uh what is it type I think you can do on an alias and it just says like run is an alias for DOT slash run so on your box you'll be doing that slash run unless you make an alias to run in your aliasis file that's how that ties together yeah so we have tests running now which is pretty cool and you'll notice that this output for running the test looks very similar to what you've probably seen when you're running Docker out or even running rails outside of Docker right but by default actually like I made a modification to this file and by the way like you know the example real sales generator maybe it'll start doing this in the future if Docker remote is enabled we'll see about that it's out of my control but if we go into test that RB what I ended up doing here near the bottom of the file is I added a warn level to the logs because you know if I do this and comment this out and you know since we did change this config file we do need to re-load things here quick way to do that Ctrl C then just up but there's faster ways to just reload the lab server if you want but whatever another time perhaps but now if we run run test you're going to see a lot more output here way a lot more output and you know that is because if you think about it the development config Got Loaded when you did a Docker compose up and just setting explicitly the Realty MV to test is not enough to make it use the test config uh well I don't know actually that's a good question maybe it should have done that by default I don't know if this is a bug or not but in any case when I look at this output here you know this is a very small project it basically just has a health check and a couple of different tests but seeing all the database queries and stuff in here it's a little bit noisy so what I ended up doing there was yeah I just set the lag level to warn and um yeah just it's nice to have uh just in case you don't want to see all those details but if you did want to see the details then you can always uncomment that if you'd like um now let's talk a little just a little bit about lock files and dependencies in general this is an interesting question right you know we have our gem file we have our gem file Lock And if you were not using Docker then you would just run your bundle installer right directly on your Dev box all of your gems would be installed a new lock file would be created in the current directory of where everything is and then you can go and commit that lock file and the gem file itself to Version Control and then you can push it up to wherever you want to push it up and another developer can clone it down they're going to get the correct block file everything is good to go all of your dependencies are set up exactly how you want them to be and you can do the same thing with Docker as well but it takes a little bit more to get that to work the way that you might think it should work and that is because we need to differentiate a little bit between Real Time versus runtime and like how volumes work with Docker now we're going to do more of a deep dive in this Docker file a little bit later in the video but one takeaway here is in this Docker file we are doing a bundle install and you know this whole entire file is basically running in the context of a Docker image like you know these are the steps to build the image itself you know these aren't executing literally on your Dev box or executing you know within a file system running in this case uh the official Ruby image which is using Debian slim Bullseye uh whatever that's a Debian variant but it runs a bundle install here and it means it is going to produce a new gem file.lock file but that lock file is only going to exist in the context of that Docker image it's not going to exist on your Dev box but now it's like well that's kind of whatever cool story like how do I get that lock file back in my devbox well uh when it comes to using bind mounts with volume or by an amounts with Docker that's basically a two-way binding so that you know you can change a file you know one of your controllers or something like that or a template and you can just go to your browser hit reload and things work like that's amazing for development probably not going to use volumes in production in that regard but uh you do need a way to volume Mount out that lock file so that it exists back on your file system on your Dev box so that you can actually commit that lock file to Version Control and uh if we go back to this run script over here I do have a little shortcut to run a bundle and install and basically what this does is there's a shortcut here and we can actually jump to this uh definition here where it is going to run a Docker compose build and you know that's your standard command that you would run when you want to update a dependency or something like that or you change your Docker file then it is going to run a Docker compose run and it's going to run um yeah it is going to run the command that we took a look at over here which in this case for yarn it's going to run JS yarn install but let's look for the bundle install instead so this one is going to just run a bundle install inside of your container at runtime where bind mounts are going to be available and what this will do in the end is it is going to produce a new lock file that's value mounted back to your devbox and now you can commit it to Version Control and then also you know going back to what's over here uh near the top of the file then yeah it just runs down at the end but yeah I can run this now I suppose let's see it shouldn't take too much time uh because gems are already built so if I just run run bundle install then it is going to fly through everything it's not going to need to reinstall things because they're already up to date and now yeah what I ended up doing here it just created a new container value mounted out the lock file made the lock file available on my file system it didn't change because there was no new gems that needed to be adjusted but uh yeah this is one way to very quickly get a lock file out to where you can commit to Version Control the same thing applies down here for the yarn install as well and uh what's interesting too is like you know you're probably used to running some other commands too let me do a Docker compose up here you know there's uh the outdated command which is quite useful here for example if we run dot slash run or just run bundle outdated you know uh very useful command just to get the latest gems available for your project you know it Compares what you have versus uh what's available on rubygeoms.org you can see in this case new version of mini text came out should probably update new version of racks or Puma's available let's Google for it I haven't updated this project yet to use rack 3 just uh want to want to wait a little bit more to see what rails does in that regard uh but yeah you know just because we're using document here doesn't mean we get to lose the benefits of being able to run some of these commands and you know if you run around if you want to run custom bundle commands you can also you know just run whatever you want to run you can do you know bundle to help Nu or you can go into you know the Run script itself and add more shortcuts here there's another one here for bundle update which we'll just go through and you know update whatever dependencies that you have in your gem file and you know generate a new lock file based on you know a new version of PG we're available that's still 1.1 or you know you get the drill you're a ruby developer so yes that is that and uh oh there is one interesting thing to go over that's related to yarn itself so when you bundle install things let's actually take a look here so let's hop into the shell I'm pretty sure it's like user local live I think or yeah local lib Ruby I think yeah Okay cool so let me clear that uh so this directory you know somewhere deeper inside of here so if we do an LS there's like gems and then inside of gems there's going to be guess what your gems well no just kidding inside of the Rails the Ruby version and then inside of gems yes okay so you know all of your gems are installed in here and uh this path here use local lib it's not value mounted back to your Dev box but uh when you're dealing with something like yarn when you do a yarn install yarn is going to create a node modules directory and we all know the running joke like node modules directories like the heaviest object in the universe but that is going to end up being a relative to wherever you ran the yarn installer whatever command it is right basically you're going to end up with a node modules directory just like sitting in your project and yeah I'm basically volume mounting in the whole directory here just so we have that nice ability to you know update something in the app directory and then it updates an in Docker right away you don't need to reboot things but going back to that when it comes to working with yarn we don't want that node modules directory to be just sitting in the you know the file system over here it would be uh well I don't know when I say it's bad there are trade-offs like it's nice to have a value mounted out because if you change one package you don't have to reinstall everything whereas if you don't have a value mounted out then you know you change one package due to the way Docker works it's going to update everything we'll get into more details about that in the docker file but the takeaway here is I've got this dot yarn RC file and for yarn itself I've actually customized where these packages get installed to like they get installed to literally like read the file system slash node modules with it which isn't value mounted into the Container because the container if we go back to that compose file over here uh over this one down here yeah this you know it only gets mounted into the slash app directory in the container so you know if we go to slash app here in the container then you can see you know this is where everything is value mounted in if I would make a change on my file system then we can see the changes reflected here but if we go to slash node modules here if I can type node modules oh you know what node modules don't even exist in this specific running container it only exists inside of the JS container or css and that's due to how multi-stage builds work we're going to get there in a bit but you know this is where they would be located in fact you know just doing things here you can do Docker acoustic Tech over here you know we can just shell into the js1 and uh oh yeah no it's not shell because I'm not using the Run script here gotta use bash instead and then we go into node modules you know there's all the node modules over here but they're not in our main rails app because they don't need to be there right they only need to be copied over and we'll get there when we get there at multi-stage builds but yeah yeah I just wanted to quickly cover that you know we're dealing with lock files and dependencies yeah let's take a look at the database.config file in this case the project is using postgres and it's very similar to how things work with my SQL if you're using the MySQL Docker image but the official Docker postgres image it will expect you to pass in at the very least look uh the username that you want for postgres to be set up as well as the password those are the bare minimums you can pass in a database if you want you don't have to and if you don't it's going to default to whatever value that you have in the user and by the way these are all exam like you would never hard code like literally your database password here you know the idea is it's going to read that value from the environment variable and then in our different environments like Dev test and prad we still read from the postgres database name but we just underscore things with you know whatever databases that we want and uh yeah we'll do translates down to is you know when you run a Docker compose up for the first time literally the first time on your machine like there's no existing volume set up for postgres then it's going to make a new user and password and yeah things will get set up for you all nice and inside you're good to go but what's really cool about this too is you know earlier in the video we were talking a little bit about you know maybe in digitalocean or whatever you want to use a managed postgres database and maybe you know that's going to be hosted somewhere different and it's kind of lame to like have to individually Define like the postgres user and the password and the host and the port and like all sorts of other different things so rails has this really cool convention you know database URL is a thing uh you know we you get the idea right database underscore URL and actually I think I have this open here somewhere do I no I don't so long story short where I wanted to go with this one is things just work and like it's also a great thing about rails where uh yeah I feel like the path has already been paved like people have done a lot of not dirty work but things to make things work for uh you and me just to use rails so with the way this works basically I'm not going to narrate the whole docs to you is that rails are smart enough to where if you modified or you create an actual database URL you define that in your in your EMV file or whatever running environment for your app to run on it will read out the various specifics about that database configuration that you have in the database URL like you know the adapter the database all these other things and it's going to merge it into whatever you have for uh your database.yaml file and this is when you know this file exists and you're using database URL the docs go into details about this but anyways like it just works in the way that uh is very intuitive right when you have the EnV set for database URL it's going to merge that into here use all the values from the EnV so a long story short you can still have this file in production but just set a database URL once in your EnV file and then yeah you can connect to a remote host for your postgres or MySQL whatever it is different password Etc you don't need to worry about setting all of these over here and you know using that Docker compose profiles thing you know you just wouldn't set postgres to run when uh yeah you do your up so you get the best of both worlds basically you can use your remote DB and it's all super easy to set up and get going yeah let's talk a little bit now about puma and cycle configs so we go to a puma.rb since we are running Puma inside of a container we can't bind to localhost by default anymore instead we need to bind to 0.0.0.0 because think about it like this like you're in the context of your container like this we we want to be able to connect to Puma from outside the container technically right like our client is a browser sitting on our Dev box it's not a browser like literally running in the container so we just bind to 0.0.0.0 so that it allows connections from places other than localhost and there is like port forwarding you can do in the compose file to restrict that a little bit more and we'll get into that a little bit later in the video but the idea here is yeah we just need to bind to 0.000. that's really the only change here very specific to Docker itself I have done other things in this file just to make it a little bit more portable on how you run the application like for example yeah you can just set web concurrency as an environment variable if it is not set then it just falls back to two times the number of CPU cores that you have just on whatever box is hosting your server so if you're running that in the cloud somewhere with uh two CPU cores then this value would end up being four because it's going to be 2 cores times two you know you can also set environment variables for rails Max threads Etc it default to five if you feel free to change these however you want they're all defined in the EnV file with documentation there if you want to take a look at some of them there and yeah related to psychic how that works really there's nothing but if you go to like the scitec initializer I just wanted to bring out real quick here that Sidekick is configured from the single redis URL environment variable it defaults to connecting to redis by default you know running in a container but you can just modify the environment variable in production to connect to a managed redis if you wanted to and then yeah you just configure sidekick to connect that and you're good to go that's it nothing really Docker related more just around you know this project itself uh yeah let's talk a little bit next up around the web console which is quite useful so if we go to here and you know let's say we go to a URL that doesn't exist we got this nice little web console in the footer of the Rails error page here and this comes by default like it's in your gem file so if we go to the gem file here down on the bottom in development the web console gem pretty sure when you do a rails new this isn't in there by default if not um yeah you can just add it there but I'm pretty sure it is but if you go in there yeah like now you can literally just start doing whatever you want like you know I don't know like hello equals world like you have a string here you know you can get the request uh parameters here so we can just do requests and you know there's everything you can start praying into details of whatever you need to pry into are really useful for debugging right if your application throws an exception you're going to see this error page you can investigate like what's up with the current user or you know this app doesn't have current user but yeah in this case I just went to a routing error to produce this here and the takeaway though is when you are working with running this inside of Docker is in our development that our beat RB file on the very bottom here I actually had to configure the web console to allow IPS from 0.0.00 this is very similar to why we need to set the buma or Puma bind to 0.0.0.0 because yeah you're just not going to be allowed to even use that uh error console if this is not set and I have a feeling now that rails is getting support for Docker out of the box this is probably going to be the new default no I can't say that for sure it's like a dhh decision there but uh yeah we did talk a little bit about this one over Discord and some solution I imagine is going to come out so you don't need to always set this one but for now time of making this video you know if you want that to work then this is what you set because if you don't set this then yeah it's just not going to work at all like you don't really get any hints on why I don't think so let's actually see that I just remember like hey it doesn't show up so okay so we restart the app what that commented out we go to the page here and there's our error page yeah you can see like it just disappears and then yeah well this is normal because I went to that but I know oh no Okay cool so you actually do see some message here saying that um yeah you just can't render that console because it's not allowed actually that is really good like now I do remember seeing this but yeah if you ever got this error like now you know you can just set it to 0.0 because again like you have different ways to limit who can connect to that through Docker itself which you can almost think of like iptables and also this is also only of course in the development config right definitely don't want to be using this one in production because remote code execution is uh a bad thing because you know you can literally execute anything from there uh okay so now let's talk a little bit about the docker ignore file yeah we're going to transition to talking a little bit more just in general about uh Docker specific files the ignore file the docker file post file a little bit um so yeah uh this one just has a whole bunch of different patterns that you wouldn't want to be copied into your Docker image when you do a Docker compose build or a Docker build if you're not using compose you know we don't want the node modules to be built in there if it were to happen to exist inside this relative you know uh directory over here we don't want things like you know your config master key to be copied in here although this project doesn't use rails credentials it uses emvs but still definitely don't want to have that in there in this case it ignores all EnV files except for an even V example file so like this EnV file really isn't commit to Version Control at all but the example file is and the instructions here for the project itself like to get going like brand new using the project then all you need to do is like literally just copy the EnV example over to the real file and then you just up build wait like five minutes and you're good to go so the readme file here I maybe should have mentioned this like it's pretty extensive you know there's there's a lot it explains a lot of things uh besides you know what we're going over here but yeah so going back to the docker ignore file just ignores all sorts of stuff that you know you wouldn't want to be copied into your image because uh it's very similar to a good ignore by the way but you know it doesn't use a good ignore but yeah like if you don't have dot get explicitly ignored in your Docker ignore like if you were to do a copy dot dot like copy the whole current directory into your image then it's going to Lug along your git directory and you know a big project with a big git directory could add hundreds of extra Megs to your Docker image there's all sorts of you know things you wouldn't want to install here and one other thing too like I am doing my best to contribute things back to rails actually I'm really happy this is my first pearly Quest pull request two rails where yeah I just added a couple different files to this git ignore file for the official generator one so you know the get directory the envs and then some other stuff related to node itself here uh yeah we don't need to have the stuff in the image because yeah they're just Dev dependencies basically or you know they produce static files that get bundled into your app but the static files that get produced are what's important not like literally lugging around uh you know es build stuff or whatever you happen to be using so yeah that's the docker ignore file oh and by the way you might be thinking like well you know if envs aren't built into the image which it's bad because you know if you were to build the EnV file into the image this EnV file could have all sorts of stuff in it you know if you're not using the rails credentials like it literally has everything database connection strings uh stripe API keys for production like you know whatever your app happens to be doing and Docker gives you many different ways to inject environment variables into a container at runtime a really popular option if you're using Docker compose at least and also works with Docker using the EnV file flag but yeah over here we can see we're referencing this EnV file uh inside of this and that just means at runtime Docker compose is going to look for an EMV file because it's named that here and then anything that it finds in here it is going to make that available to your process like in the container so if we do um Docker compose well I'll use the Run script right we do run shell we can just run the EnV command standard uh you know command line tool and it is going to Output all the environment variables available to that container and we can see you know there's a couple things added by Docker itself like the host name and some other stuff that's set here but you know there's the webkinator like this is in the EnV file that's in the EnV file that's in the EnV file you know a lot of these are and uh yeah we just have them available to us and what's really cool this is now like becoming Super Side topic territory but you know if you run a Docker compose config that is going to parse your composed at yaml file and basically give you like exactly what Docker compose will run so in the end like it converts that EMV file really to key value pairs for the environment we can see everything in lines here it's the exact file that gets run so just a little tip I think I have a blog post about that one don't know if I made a video about that one but I just wanted to drop that in there so yeah now let's talk a little bit about the docker file now I've done lots of videos in the past about multi-stage builds in general don't want to get into all the great details about that here but uh let's start with some more Basics before we get into multi-stage building and stuff because we will touch base how it relates to rails at the very least how long is this video by the way 39 minutes not too bad so far but yeah we can see here the front line you know this is the official Ruby image available on the docker Hub currently using the 3.2.0 and then we are using the slim variant here now we could not use slim and instead of just going to use Debian Bullseye that's the code name for Debian 11 latest stable version at the time of making this video this will change to Bookworm I think as Debian 12 but um yeah the slim version will make your Docker image quite a bit smaller so if you don't use a slim version Debian by default install something like I don't know 480 or 550 actually I had the exact number here because I did create an issue here for rails to you know consider using Debian slim and multi-stage build to do substantial shrinkage of their Docker image that gets produced because the official Docker file from rails will produce a 1.6 gigabyte Docker image which is pretty big but using Debian slim and multi-stage builds we can get that down to 600 Megs and uh this project itself it's actually I think 472 something like that so if I do in Docker image LS we'll grep hello rails that's the name of the containers here we can see yeah 472. so yes the CSS and JS ones are much larger but these are you know lugging around the whole node ecosystem there the node red time everything in node modules but you know when we ship this application to prod it's only going to be using this 472 Meg so that's it these are actually built off the same exact image they just have different container names you know you don't need to like times this by three because Docker shares base images maybe it'll get into a little more details about that one but um yeah going towards that like this is using Debian slim 472 Megs and uh down here somewhere in this issue whereas I have a chart some numbers here yeah if I were to just not use Slim in that project for that one then that whole thing jumps up to 960 Megs and that is because um where do I have this here yeah let me zoom in a little bit more is that readable yeah pretty readable okay so normal Debian installs 528 apt packages which you can verify by running this command here by the way we'll run it in a second here but Debbie and slim only installs 211 of them so we're talking like you know 300 plus package difference there which means like that's a hell of a lot of packages that you probably don't need because you know you only need to basically have the packages that your application needs you don't need to have you know every Library under the hood so let's uh run this command though just to see the output so we can see you know in case you didn't know like that command just lists every single package that you have installed so basically you know with Debbie and non-slim you end up with like a million more lid packages here and uh yeah do you need all of them probably not but you need enough to make life easy for you as a rails developer because you know there are going to be a couple of gems that you have that will require some form of uh you know it's going to be natively compiled like with a c compiler where you need certain Library headers and other files to exist for that to work and I did call that out here at the bot yeah okay so you know if you're using bcrypt or open SSL or icon V and even sqlite as well like you could use slim out of the box and the only thing you need to install is build essential and now build essential is an app package and it just has a whole bunch of different other packages that are pretty common when building C dependency so there's like you know GCC and other compilers and other Library files it's a pretty beefy package but it's a good mix of like man I don't want to track down like every dependency for open SSL or every dependency for Icon V just give me this thing for like 100 Megs and like not worry about it so in my opinion it's worth a trade-off but you could technically not use this and instead track down literally everything for everything and end up with a slightly smaller image but in my opinion that's not um there's bigger fish to fry once you're at that point but yeah if you were to use something like PG you need to install the PQ deaf that's a common thing again if you're not using Docker deploying to Native Linux these are things you would need to do anyways if you're using MySQL instead then you can install this lid Maria DV compatible same thing fortunately these two libraries are extremely small they're like one Meg each and yeah I've got some outputs here just showing that somewhere yeah one like for each one now there are certain packages that are pretty big like unfortunately the VIPs is uh kind of I don't want to say Maniac but yeah it adds a lot like in my example app here it adds 130 extra mags just for lid VIPs but going back to this chart chart here yeah so yeah it's about 300 Megs when you're using the non-slim version of Debian um yeah it adds up and libid epis is a dependency for to use something like active storage you want to use some image processing then that's the underlying apt package to just make that gem work the way it needs to work but um yeah so that is just around you know the benefits of using W and slim that's not currently in the official rails Docker file but you know once some folks uh I don't know some someone reviews this like maybe that will be the first step because moving to Debbie and slim is pretty easy uh that's like a couple line pull requests and not too bad the other thing is yeah multi-ch builds is a little bit more involved um well it's not crazy but you know you look at this Docker file and it's like uh like you know 79 lines like some comments but um the real takeaway with the multi-stage build is like you can create two separate stages in a single Docker file so there's a from line here for we name them assets and by the way other videos I've done in the past like my Docker contact goes into a lot more detail feel free to really go into that one for the great details but yeah we've got another friend line here which is for the application itself and you know the real idea here like the short version is you know we install the latest LTS version of node um node 18 at the time I'm making this video I'll bump this up when it needs to be we also install no or yarn as well and yeah so we end up doing a bundle install because ultimately this is where we do our rails assets pre-compile we need the Ruby environment in the stage but it also runs the yarn install here and this is going to produce all these dependencies you know we can run our JS and CSS containers that we saw over here you know es build and Tailwind Watchers in this specific case but all these files exist in only this stage you know that's why this output that we looked at before um well I guess way before where is it now whatever you know the one that was like a couple hundred Meg difference not important that is because there's a lot more files in this specific stage and well I'll show you how you run the different stages in a second here but the real takeaway is inside of our application stage we end up only copying in things from a specific stage to this stage here so uh in this stage there actually isn't a bundle install command being run at all because we already run bundle install here we know that it produces everything in user local bundle or whatever then we can just say look from the asset stage that's the name that we have at the very top of the file here you know as assets then copy that whole bundle directory to this stages bundle directory done okay cool and then for the public stuff same thing like es build and Tailwind whatever they're going to produce uh bundle JavaScript bundle TSS file we don't care about the node environment we just care about those tiny little you know 50 kilobyte CSS file or whatever it happens to be so we just copy those files over whole entire node runtime not involved whatsoever in this in this stage so that is really really nice when it comes to multi-stage builds and using them is is quite easy too like in our compose the ammo file here we say that the target is app because this is you know well we didn't go over this Alias as an anchors uh pattern with um yamla it's in other videos but we have this x app over here defined but if we go down here yeah we can see the worker and the web are both using that default app here whatever it's a way to get common properties into multiple things the tml future not really super specific to Docker pose other than like you know these custom attributes that were named here but yeah for the assets one we just say the target is assets so it's targeting that build stage this one is only going to Target the other build stage so you can very easily switch between them and cool and by the way since we're in here well no we'll get to that afterwards so yeah let me go back to the docker file here so that's multi-stage builds like the super short version of it it's just a way to have a more optimized Docker image in the end but yeah having a smaller Docker image is pretty important right because typically you would be building your images in some type of CI environment right maybe GitHub actions or whatever you happen to be using and then you would push that Dockery image to some type of Docker registry right maybe the docker Hub maybe you're using ECR if you're on AWS like digitalocean has their own like there's also a GitHub has its own as well like long story short you're going to be shipping a couple hundred Meg files somewhere and then in production you would pull down that image from your Docker registry using you know Docker compose pull or you know you can pull it with a regular Docker without compose as well but you know you need to pull down that like 400 Meg image so you know having a 1.6 gigabyte image is not too fun especially compared to 400 or 500 Megs and uh well Docker is pretty smart though like the way that it works in the sense that like once you've got that initial image pull like if I were to just I don't know make a patch to my application add two models and a controller or something like that then it is not going to need to re-pull like 1.6 gigs it's smart enough to do almost like a git diff in that the sense that you know it's only going to pull basically the layers that change and uh that's a pretty long conversation around this but oh there's a couple other things again this Dr Phil will go over but maybe we'll get there just naturally from talking about this file but yeah in any case having a smaller file is just beneficial um just especially if you're running that scale where where you know you might be running dozens of different copies of your application through kubernetes or something and I don't know maybe you're running a fargate and you don't have your own managed ec2 nodes and fargate at least at the time making this video you know it needs to pull down the whole image every time so yeah it's a big penalty if it's um lots of gigs so let's see where are we at next here okay yeah just talking about Docker layers and just how they work in general notice here that uh we are copying in every single thing from the current directory into the worker so the work tour is set up here slash app these are Docker Basics I kind of want to skim over them because this video will be never ending if we go into the Gory gray details but the real takeaway here the important one is we've separated out copying in our gem file and our gem file lock by the way that's what the star is there it's going to copy both of them into the slash app directory here and then it runs the bundle install you know then it also runs a yarn install and you know same pattern what uh package.json and yarn you know there's a couple different yarn files that want to copy in there but yeah we are doing these as completely separate steps because having uh we could theoretically have combined like the bundle install and and yarn install but we don't want to have all of our yarn packages reinstalled well in this case it would because the ordering but yeah the way Docker works is you know we want a Docker layer to be created for this gem uh well this producing of gems when we run a bundle install but like you know if we were to ever change our application application code later that doesn't mean we should always bundle install again like you know if I add a comment to some controller or whatever like it would be crazy having to run a full-blown bundle install just because I modified some code that I had so it's a really common practice to separate these things out to where yeah we can run a bundle install then we copy in our application code later because if we only change our application code then all the stuff above here is not going to run only this and down is going to run so yeah if we were to change our gem file and like reinstall or get a new Lac file then everything down here is going to run but let's only do that when we have to so yeah in this case it's kind of unfortunate that these things need to be ordered in in some way so it's like you change gem it's also going to do a yarn install you know I chose the lesser of two evils here and just put this one after because typically uh running yarn install is a lot faster than bundle install so that's why I put this one in the bottom there but yeah that's just one thing to think about when setting up your Docker file and uh yeah let's talk next up a little bit about non-root user because you may have seen like the CH Ruby or CH own Ruby Ruby here couple different spots in my opinion it's a really good idea to run your containers as a non-root user right we don't want the Puma web server to be running as root when it's running you know just serving uh web application requests now it is running in Docker so you get a little bit more security from that but still you want to run these things as a non-root user and I've also done a lot of videos about this in the past too I'm going to link to that but you know the one minute version now basically is inside of each stage here you know we create an actual well in this case let's go down here it's going to be a little bit easier to explain because there's more moving Parts with the node one but the same exact concept but uh yeah over here we are just adding a new group for Ruby we are creating a new user called Ruby we are setting the uid and GID to be a thousand by default you know we want certain things to exist for this user like we want a home directory just because I've noticed in the past where uh certain packages may expect a home directory to exist and if it doesn't then like things get pretty wild so we might as well create one and then uh yeah then we just Chown this uh work Dura directory to make sure that the Ruby user owns it recursively and then at this point we just switched that Ruby user and everything after that gets run as a review user including our rail server here which is going to run Puma by default but when you copy in files you do need to Cho in the files with a specific permission here so even if you have something like use a ruby set you know you still need to do this yeah it's really nice to have all that set up there and what's interesting is and and I can see the appeal of dj2's thought process around not doing this by default because think about it like if you're brand new to rails like maybe you're just getting started you're not like already a veteran with Docker you start seeing like like what the hell is a uid you know like you're just not going to know these things and if you're looking at a generated file there's going to be a lot of different questions but you know there is a complexity that needs to leak out if you want something to be secure right if the docker file they're producing is made for production running things as an unuser is really important and uh yeah in other videos like I go over what a uid is a GID like how it works with a thousand like you know I don't have a ruby user on my Dev box but due to the way bind bounce work with Docker uh it will bind Mount based on the uid and GID so in my case on my devbox using wsl2 by the way if I run the ID command here we can see my Nick user uid and GID is a thousand those line up so on my devbox all the files are owned by me like Nick you can see here but in the container they actually end up being owned by the Ruby user so you know if I can do on the shell here and I can run the same ID commands here you know we've got Ruby Ruby thousand thousand and then I do the ls here and we can see that all the files are owned by Ruby Ruby everything works great now if you're using Docker desktop on Mac OS and windows as well uh you don't need to really worry about this at all like it's just going to work but if you're deploying to production analytics server then you do need to manage these uid and gids and you know if you're setting up a VPS on digitalocean Etc or your own hosting provider like whatever you're doing you know you can generally control the users that you create you know you might make a deploy user and that's going to be the first user created on the system so they're going to get one thousand one thousand for the uig and GID and it's going to work fine but what if you're working in an environment where you can't control that it's very possible that your user will not be these values here and you need to customize them and it's a build time type of thing so in the cnv file you know we've we've got environment variables set up for that so you can just comment them out when you want and then in the compose file and again the video goes into details about this uh yeah it defaults to this but you can customize them at build time these are build arguments and boom Oh there's one thing I wanted to talk about too in the docker file but anyways yeah like multi-stage or running your process as an Android User comes with a little bit of complexity but in my opinion it's an you know it's a necessary one but uh yeah going back to over here uh just before we move on to what are we going to move on to next oh yeah oh yeah no cool it was it was this anyways so we're inside of this uh asset stage here right this is your node environment that has everything set up and good to go and typically you know you're deploying to production you wonder you want to run a rails assets pre-compiled and that's going to you know digest all of your static files you know putting the md5 hash in there and it's going to do all sorts of good stuff and this command takes a little bit of time to run you know a couple of seconds for a smaller project maybe a little bit more for a bigger one I have this thing configured now so that it only runs when you are not running uh rails EnV in development so basically you know if you're running in your staging environment or in production or you know whatever non-develop environment that you have then this is going to get run and uh you know you might be thinking like well it's going to get running tests like no like typically you wouldn't build a Docker image when your rails EnV like is going to be set to test right so you know this EnV file here you're never really going to be setting rails EnV to test and then running the docker compose build like no you just set the test at runtime like we saw before and there's also one interesting thing here too so to run a rails assets pre-compile you need your secret key base to be defined but it doesn't need to be defined to anything that makes sense like I just put a dummy value here I forgot where the pr is exactly but there is already a change in rails Master now that adds a new environment variable that you can set to more explicitly say that you're using the dummy value like what is it it's something like I'll overlay it like while I'm editing the video but you know you no longer need to do this you can set like a dummy like little different environment value here um or even key value not important but just Minor Detail I'll change this project to use that one sec it's included maybe inside of rail 7.1 but yeah it's pretty nice here so and this is going to create uh these pre-compiled Assets in the public directory that's why we saw down here when we we just copy those from the public directory into slash public which is a little bit confusing uh but we'll see how that ties together very uh quickly here when we jump into the entry point which I think is the next topic of conversation here which it is so in this bin directory here I've got an entry point and didn't mean to open this entry point there's a lot of stuff in here that uh is a work in progress I don't think I'm going to even commit this stuff probably should have removed it uh before recording but I forgot to do that so let's take a look here at the entry point file in the actual GitHub view here doesn't matter because I'm not going to be looking at too much code here but uh what this ends up doing is the entry point script runs when your container starts so every single time that you start your container this entry point script is going to run and one thing that it does here is if we go back to this Docker file this uh copying things from the node modules to slash public you know this is where your bundle TSS and JavaScript and other static files are going to be and this entry point script is going to recursively copy that to the slash app directory but just due to the way the copy came in works it is going to be copying them from slash public to slash app slash public and now uh those are going to be volume mounted L thanks to this environment variable that we have or actually the compose file over here I should say if we go to here you know that is going to Mountain slash public to slash app slash public or dap slash public I should say like the current working directory of public anyways like long story short you put all this together and you now have a way to persist your static files to your Docker host and that's really important if you're doing something like um you know maybe doing a single server to play with Docker compose we do this all the time and you know maybe you want user uploads to be served from nginx or you want your public files to be served by nginx you know regardless of whether or not you're using the file uploads from users and whatever we'll get into that next but yeah we want there's a value mounted directory to exist so that nginx can then have its root configure to serve those public files and that is good to go and if you do have something like you know user uploads or something like that and you're not using S3 you will not lose those uploads because yeah when you start and stop a container all the changes are going to be they're wiped out unless you use volumes so yeah this little trick here allows us to have our public uh assets pre-compiled to a specific directory that's not going to get collaborated by the value mounds and then at runtime we move them over into the right spot and there's another really interesting thing that came about here dealing with sprockets and manifest files so when you run your rails app in production mode and you do your pre-compiling of assets it is going to create a DOT sprockets manifest star and then some get you know some md5 shot or whatever and then the Json extension here and this manifest file has basically the key value mappings of like you know I want to load logo.png but really you know it's going to be logo Dash and then some md5hash.png and yeah the Manifest file has uh that mapping in there so that your template helpers when you do an an image tag know what to look up and every time you generate a new set of assets you're going to get a different manifest file with a different md5 hash and I don't know if this is a bug with rails or an oversight or something but it will not use like when you you know do your rail server command or whatever to bring up room like whatever like it is not going to read the latest manifest file I don't know what it uses to read it uh but it'll it will read one of them and what I found in practice is that uh it will you know you'll you'll end up with your old assets being loaded instead of the newest ones so at runtime what I do here is uh I basically just delete all of the old sprockets manifest files except for the latest one that got created and we can see here you know there's quite a bit of little shell scripting to do this but let's ignore the comprehend if condition for now it's actually easier than it looks because this find command here if there is no sprockets file generated initially then it was throwing an error like find would literally throw an error because there's like nothing to find I guess I'm trying to think like because I did this code like so long ago but anyways uh this Compton just wraps that condition so that compgen in itself it's smart enough like this is going to ultimately say like look if there are files listed here then we'll do the find and if not we're just not going to do any work at all and we can't use like you know shell scripting with there is a an if condition you can run like brackets Dash F and then you can look to see if a file exists and like that's a very easy test but that doesn't work when you're dealing with a matched pattern instead of one specific file but Compton does work with the match pattern so anyways like yeah we just run find here and it is going to go through all the Manifest files that you have and then it's also going to delete them except for files that are not like this one here and you know we took a look here at this copy here that has like the absolute built-in like latest versions of all of your bundled files that's not value mounted so like they're baked into the image there we know that the latest ones for sure so yeah we just get the base name of public assets brackets blah blah it's the latest one so we always end up in a scenario where our application is serving the latest assets so that if you were to push a new build with your new assets and then restart your container you're going to get the correct assets there no matter what and um yeah I don't know it's a little bit noisy to have to have this in here but again it's one of those things like this complexity needs to happen unless rails itself were changed to always load the latest sprockets file and then if that were the case all of this code could go away who knows maybe um you know I'll bring that up with dgh like we're not best buds or something like I just talked to him very briefly over Discord but you know for working towards making some changes to the official rail stuff like yeah this feels like a reasonable thing to maybe consider doing or you know what is that new project that they're working on like prop Shaft or whatever like the next evolution of sprockets like maybe that could be handled there like we'll see anyways like there is uh that type of thing there and yeah going back to the entry points here what else was on here yeah there's one more thing to go over maybe I guess around like well what about database migrations like you would think like well if this entry point script runs every single time your container starts like maybe it's a good idea to throw in like a DB migrate like right here you know and the official rails generator does do that in fact I actually made a pull request to it to change one little minor thing here around uh yeah so we only want this migration to run like when you're running the rail server so this little if condition here which is now part of the generator code will say okay cool when the entry point has started uh if the actual command that you're running is you know bin rail server then we'll run a DB prepare like this was already created by the ideas to run a DB prepare and that's it so if you were to run something like you know we go back to our code over here you know when I was running uh like the docker compose exec uh web and then rails console like we wouldn't want the database migration to be run when we just open up our rails console so you know this little patch here basically just says that uh yeah it you've got to be running the real server then we'll do the migrate or set up your database uh otherwise it gets skipped and you know he already had that in a different way here but I just started using uh well there's other components of the pull request not important but takeaways is that and I don't know it's not my spot to convince dhh or other folks not to put migrations in this entry point script you know maybe we could have a more in-depth conversation about that one the reason why I don't like running migrations inside of this entry point script is you know it runs every single time your container starts and well I don't want to look at here look at it back over here and if that needs to happen then you know let's say that you are running in I don't know like a kubernetes environment where you might have 10 replicas of your application being served the rails DB migrant command is smart enough like it's an item point in command you can run it eight times or 12 times or two times or one time and you know it's always going to produce the same amount of results there it's not going to like try to migrate your database 12 times because you have 12 copies it will just do essentially a no op for the other ones right but you know database migrations especially at scale they're scary enough that like I don't want to depend on that and I think like objectively there is something worse about that style of having all of your replicas run hey migration uh even if it's not doing the work on any of them except from one of them and and it's basically like if you've got 10 replicas of your app running and let's just say your migration fails for whatever reason then kubernetes is going to try to do a rolling update and it's gonna you know roll out your application to those 10 copies and you know it's going to try to migrate all 10 of them then the migrate command is going to fail then kubernetes is going to be like oh all right like and then you know it's not going to roll back the old version but like the older version of your app will just continue to run and like basically that rollout that you just did failed and I think there's a cleaner way to do that uh you know if you're dealing with kubernetes specifically you know if you're using Argo CD to manage your deployments or maybe you're using Helm like directly or something like that you know in either case like they have the concept of running Hooks and you can actually have a kubernetes job that gets run as essentially like a pre-deploy hook you can say I'm just you know using common terms for here um but yeah it'll run before your application deployment gets rolled out and then that job will only run once like it doesn't matter if you have one copy of your app or 100 it just gets run before your app even begins to even think about being rolled out so it's a really clean spot to have that because it just means one job will get produced it's either going to fail and then in which case like everything stops there and you know your old app will just continue to run or it works that job finishes it's like cool migration is done now we're going to go and roll out all the replica application and it's pretty neat too because even from like a logging and metrics point of view it's nice to have that job being logged as like you know a migration occurred now now now now now like you can get metrics about those specifically like yeah it's really nice and um yeah even for single server deploys I don't know like I've always done deployments using git post receipt Flex so it's like you push to the server and then a shell script executes and then things happen and it's not very difficult to uh do your Docker compose you know up Dash D or whatever to run your application uh in the background in production using compose and then right after that you just run your you know you can literally with the Run script just run uh yeah you can run um you want to clear that we can do a run rails DB migrate just like that as part of your deploy script now in this case there's nothing to migrate here but that's all that you would run there and you're done like that's a script that just automatically runs like you're not taping these commands here so I don't really see a huge value in having your entry points uh run that automatically because there may even be a case where for whatever reason like maybe you don't want to run the migration as part of the application deploy you want those to be separate you know if it's in this file like you'd have to like I don't know comment out your migration rebuild a new image and then like push that without that and then do your real application change like I don't know it just gets more complicated so anyways you know that's why I don't like to have uh that inside of there but again never really had that conversation with anyone from the rails core team or dhh or whatever so maybe they're on board with removing that at some point but not my decision but I will bring it up so yeah now let's just close things a little bit off around uh looking at the compose file in a little bit more detail not too much because this video is already crazy long hour and 10 minutes Jesus okay yeah so over here uh we have the web service running on the worker service running and what's interesting with Docker compose is that you know we are sharing these common properties that are defined up here and uh we are using the same exact image for puma and cyclic in a specific case here but when it comes to running sidekick you know we just add this little command property here part of Docker compose so we get the benefit of being able to yeah share the same image but run different commands you know sidekick becomes its own thing as a worker Puma is its own thing with web and you know if you were running kubernetes or whatever you would have uh different deployments and yeah you can scale these things and independently and the only thing that changes here is we just run the cycle command instead of puma and the entry point we actually in this case like you know my entry point doesn't you know it's only dealing with static files and sprockets and stuff like that and there is none of that when we're dealing with psychic so I just null out the entry point by setting it to an empty list here and if we go back to the docker file very bottom of the file like this is the default command that gets run if you were to not supply the command property so in this case like sure yeah let's run rails uh the Puma app server by default but then for the worker we do command and same thing is happening here for uh cable so we just want to run the cable server which also was using Puma with a different config but same exact concept there as worker slightly different command than the normal rail server and then for JS and CSS same exact thing like we've got you know this assets one over here you know this is building that node image there with the assets Target and then down here we're just running the yarn build or yarn build CSS depending on you know do we want es build or do we want Tailwind or you know if you want to use bootstrap instead you can change those things around these commands wouldn't need to change that's more of just uh you know gem file changes and application changes but that is how all of that ties together and yeah you'll notice in this file there's a lot of variable interpolation you know this is just basically reading environment variables in here I think this is a great idea I've gone into Super details about that uh in previous videos Dr contalk Etc you know one useful one about postgres is like yeah we can just Define them in here we never have any hard-coded values and life is pretty good when it comes to that and yeah I touch base on this one a little bit before earlier in the video and we're almost done by the way uh yeah I really really really really think there's a lot of value in having a single Docker file and a single compose file that you can use in Dev CI and prod and the only differences really come down to a couple of different environment variables like when I want to run rails EnV and prod like I just built my image with production set and that's it from in Dev like well it's just set to Dev instead and then you know we went over the profiles how you can incrementally or choose to run different containers when you want but it gets way more complicated if you had something like a Docker composed Dev file and a prod file an FCI file and three different Docker files like it gets really really complicated to maintain all of that stuff over time especially with code editors now like I don't use vs code but you can have a pretty good Dev experience when you are using remote containers or vs code like for example you know if you want code complete using like a language server you can do that it's smart enough to know that you know your Ruby runtime and all of your gems and you know JavaScript stuff it's sitting in a container so remote containers at vs code let you do that I don't know if Ruby mine does you know it's not an editor that I use but it might uh but yeah you know basically where I'm going with that one is maybe you don't need a separate Docker file but just a whole bunch of Dev dependencies in there but that said very long video sorry about that in advance but yeah that's basically everything I guess when it comes to running rails and Docker if you have any questions about any of this let me know in the comments below I'll do my best to answer all of them and uh if you like the video please give it a thumbs up it really does help a lot and again you know just to close things out this is not me trying to say like oh use my project instead of the official rail stuff uh I'm actually looking really forward to the day where you know maybe a number of these patterns are rolled into the official Docker file because it means like I don't need to maintain this stuff then like I'd rather it be maintained in rails I have no problem contributing to that uh with or without though still you know less is more in this case and you know I'll still probably always run this example app here because you know it does tie in quite a few opinions right yes build and tailwind and uh Docker compose you know a lot a lot of different things there uh but yeah with that said thanks a lot again and I will see you in the next video
Info
Channel: Nick Janetakis
Views: 7,442
Rating: undefined out of 5
Keywords: rails, ruby, docker, compose, sidekiq, tailwind, esbuild, postgres, redis, development, production
Id: nKiZz8TNHuU
Channel Id: undefined
Length: 71min 50sec (4310 seconds)
Published: Tue Jan 03 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.