[{"data":1,"prerenderedAt":559},["ShallowReactive",2],{"\u002Fblogs\u002Fusing-github-actions-to-deploy-to-heroku":3},{"id":4,"title":5,"body":6,"coverImage":547,"description":549,"extension":550,"meta":551,"navigation":552,"path":553,"publishedOn":554,"seo":555,"sitemap":556,"stem":557,"__hash__":558},"content\u002Fblogs\u002Fusing-github-actions-to-deploy-to-heroku.md","Using GitHub Actions to Deploy a FastAPI Project to Heroku",{"type":7,"value":8,"toc":535},"minimark",[9,27,36,39,42,50,56,65,68,73,76,79,82,85,101,104,107,114,117,121,125,128,148,167,177,180,183,187,190,193,204,213,226,229,232,241,244,263,270,277,298,307,313,326,336,367,374,377,380,383,390,393,407,410,446,449,455,462,465,469,488,491,506,509,517,520],[10,11,12,13,20,21,26],"p",{},"I build Python projects & host the source code on GitHub repositories quite\noften. And thanks to ",[14,15,19],"a",{"href":16,"rel":17},"https:\u002F\u002Fgithub.com\u002Ffeatures\u002Factions",[18],"nofollow","GitHub Actions",", I\nneedn’t worry about Continuous Integration needs either. While\n",[14,22,25],{"href":23,"rel":24},"https:\u002F\u002Fwww.heroku.com",[18],"Heroku"," takes care of my Continuous Deployment needs.",[10,28,29,30,35],{},"But, there’s a problem. Heroku doesn’t provide a straightforward way to deploy\nthe project using GitHub Actions. I need to download\n",[14,31,34],{"href":32,"rel":33},"https:\u002F\u002Fdevcenter.heroku.com\u002Farticles\u002Fheroku-cli",[18],"Heroku CLI"," to do it instead.",[10,37,38],{},"And if you’ve been reading my articles, you would know I’m quite a minimalist. I\ndon’t like adding more bloatware & extra dependencies to my development machine\nthan what’s needed. Hence, I had to look out for an alternative.",[10,40,41],{},"My current development process is to commit changes on my local machine & then\npush it to remote. The remote here is usually a GitHub repo with GitHub Actions\nconfigured to trigger a suite of tests. And if everything passes, deploy the\nproject to production. Pretty standard practice & nothing fancy over here.",[10,43,44,45,49],{},"But the caveat is Heroku CLI uses ",[46,47,48],"code",{},"git"," commands to push code to Heroku's\nremote. So, it's pretty much like pushing code to a GitHub repository. But, with\nno robust CI\u002FCD pipelines. I could configure Heroku to deploy when the tests\npass On GitHub. But it's best to stick to standard practice & configure\neverything under one roof.",[10,51,52,53,55],{},"Also, invoking ",[46,54,48],{}," commands on a remote machine doesn't sound like a good\nidea. So, I felt sharing the techniques I use to circumvent this tricky\nsituation was the right thing to do.",[10,57,58,59,64],{},"So, we’ll be using GitHub Actions to configure a CI\u002FCD pipeline. And,\n",[14,60,63],{"href":61,"rel":62},"http:\u002F\u002Ffastapi.tiangolo.com\u002F",[18],"FastAPI"," to build our hypothetical REST API.",[10,66,67],{},"And, without further adieu, let’s dive in & learn how to do it.",[69,70,72],"h3",{"id":71},"things-to-know-before-deployment","Things to Know Before Deployment",[10,74,75],{},"Heroku’s design reflects the need for simplicity & reducing complexity. Its\nusers aren’t expected to know in-depth CI\u002FCD concepts & practices. But as long\nas the users are well-acquainted with Git commands, they’re good to go.",[10,77,78],{},"In other words, deploying a project to Heroku is as simple as pushing your code\nto GitHub repos!",[10,80,81],{},"You would develop your project & then push your code to the Heroku remote. It\ntriggers a build process which sets up a web server on their remote servers. In\nother words, deploying projects couldn’t get any easier than this.",[10,83,84],{},"But there’s a drawback to this process, Heroku doesn’t provide a robust enough\nCI\u002FCD pipeline. There’s no way to keep a check on any breaking changes or bugs.\nThis is where GitHub Actions come in handy.",[10,86,87,88,91,92,97,98,100],{},"So, we’ll be using the\n",[46,89,90],{},"[heroku-deploy](https:\u002F\u002Fgithub.com\u002FAkhileshNS\u002Fheroku-deploy)"," Action by\n",[14,93,96],{"href":94,"rel":95},"https:\u002F\u002Fgithub.com\u002FAkhileshNS",[18],"AkhileshNS"," to deploy the project. And the said\nAction is a NodeJS wrapper around basic ",[46,99,48],{}," command invocations. These\ninvocations are exactly like what you would use with Heroku CLI instead.",[10,102,103],{},"Further, to keep things simple & to-the-point, our FastAPI app is a single file\nwith no more than 8 lines of code!",[10,105,106],{},"Besides that, Heroku also requires some extra files for the build process to\nwork. And these files are also pushed to the Heroku remote as well. They’re\nplain-text files with information for Heroku to parse during the build phase.\nYou’ll find more details about them later in the article.",[10,108,109,110,113],{},"Additionally, the ",[46,111,112],{},"heroku-deploy"," Action also requires an API key for\nauthentication. So, ensure you've it along with the project's name.",[10,115,116],{},"And with all the prerequisites taken care of, let’s develop the project now.",[69,118,120],{"id":119},"putting-everything-together","Putting Everything Together",[69,122,124],{"id":123},"our-simple-fastapiproject","Our Simple FastAPI Project",[10,126,127],{},"With all the prerequisites set up & gathered, here’s an overview of our little\nproject.",[10,129,130,131,134,135,138,139,143,144,147],{},"To start with, the FastAPI project is pretty simple with only 8 lines of code!\nIt’s source code is detailed in the ",[46,132,133],{},"main.py"," file. And it's configured with\nroutes to return a set of JSON responses when queried. The said routes are; a\n",[46,136,137],{},"\u002F"," ( ",[140,141,142],"em",{},"or root",") route & a ",[46,145,146],{},"\u002Fhealthcheck"," route. The latter of which has some\nsignificance in our CI\u002FCD pipeline as you'll see.",[10,149,150,151,154,155,158,159,162,163,166],{},"The root route returns a JSON response like this; ",[46,152,153],{},"{\"message\": \"Hello, World!\"}","\nwhen queried. And the ",[46,156,157],{},"\u002Fhealtchcheck"," route acts as the last-line-of-defense for\nthe REST API. But it also returns a JSON response for better user\ninterpretation. So, if you invoke a ",[46,160,161],{},"cURL"," command to this route, you should get\nback ",[46,164,165],{},"{\"message\": \"Everything, OK!\"}"," response back.",[10,168,169,170,138,173,176],{},"We’ll configure our CI\u002FCD pipeline to query the health-check route to check if\nit’s still up & running. Failing to do so which means returning a ",[46,171,172],{},"400",[140,174,175],{},"or\nsimilar",") response code will invoke a roll-back. Hence, our REST API in\nproduction will always be up & running regardless of any breaking changes or\nbugs creeping in.",[10,178,179],{},"That said, here’s what the source code for our REST API will look like:",[10,181,182],{},"Our simple FastAPI application",[69,184,186],{"id":185},"configuring-the-github-actionsworkflow","Configuring the GitHub Actions Workflow",[10,188,189],{},"With our REST API built, let’s configure a CI\u002FCD pipeline for it. The pipeline\nis pretty standard & is nothing fancy. On every Push and\u002For PR event, it’ll\ninvoke a series of tests followed by code quality checks. And if everything\npasses, the workflow will invoke the deployment process as well.",[10,191,192],{},"But before discussing more about our pipeline, let’s learn a bit about GitHub\nActions.",[10,194,195,196,199,200,203],{},"It’s easy to confuse GitHub Actions as “ ",[140,197,198],{},"yet another CI\u002FCD tool","”. But, it’s\nnot. GitHub defines it as an automation tool for all software development needs\none can think of. And, there’re many preconfigured Actions provided officially\nby GitHub and\u002For the community as well. One such Action we’ll use for our use\ncase is the ",[46,201,202],{},"AkhileshNS\u002Fheroku-deploy"," Action.",[10,205,206,207,212],{},"We’ll trigger our workflow on every push event. But you can configure it to\ntrigger on other events as well. You can find more info on the list of\n",[14,208,211],{"href":209,"rel":210},"https:\u002F\u002Fdocs.github.com\u002Fen\u002Factions\u002Freference\u002Fevents-that-trigger-workflows",[18],"events that triggers a workflow","\nit’s official documentation.",[10,214,215,216,221,222,225],{},"Workflows are further configured through ",[14,217,220],{"href":218,"rel":219},"https:\u002F\u002Fyaml.org\u002F",[18],"YAML"," files. And,\nthey’re stored under the ",[46,223,224],{},".github\u002Fworkflows"," directory which is also version\ncontrolled. GitHub will parse these YAML files for instructions on how to set up\nthe environment.",[10,227,228],{},"And here’s what our workflow looks like:",[10,230,231],{},"Our GitHub Actions workflow for deploying the FastAPI project",[10,233,234,235,240],{},"GitHub Actions workflows have to adhere to a specific syntax. And explaining the\nsyntax is a bit out of context of this article. So, you can refer to the\n",[14,236,239],{"href":237,"rel":238},"https:\u002F\u002Fdocs.github.com\u002Fen\u002Factions\u002Freference\u002Fworkflow-syntax-for-github-actions",[18],"official docs","\nfor info to learn about the syntax.",[10,242,243],{},"That said, let’s understand how we can customize our workflow.",[10,245,246,247,250,251,254,255,258,259,262],{},"At the top of our ",[46,248,249],{},"main.yml"," file is the name of the workflow signified with the\n",[46,252,253],{},"name"," keyword. It's followed by the ",[46,256,257],{},"on"," keyword which instructs GitHub Actions\nto trigger the workflow on ",[46,260,261],{},"push"," events.",[10,264,265,266,269],{},"There’re way more “ ",[140,267,268],{},"on event"," “ keywords available to trigger workflows. So, do\ntake a look at the documentation & configure your pipeline according to your\nneeds.",[10,271,272,273,276],{},"Continuing on, the ",[46,274,275],{},"jobs"," section of the workflow is pretty much the heart of\nthe pipeline. This is where GitHub will find instructions on which Actions to\ntrigger & when. For our needs, we defined 3 jobs & they'll run in parallel\nunless explicitly configured not to do so.",[10,278,279,280,283,284,287,288,291,292,294,295,297],{},"The jobs are configured to run on the latest version of Ubuntu. And\nadditionally, the ",[46,281,282],{},"deploy"," job is dependent on the other previous jobs. So,\nwhile the ",[46,285,286],{},"test"," & ",[46,289,290],{},"linter"," jobs run in parallel, the ",[46,293,282],{}," job will wait\ntill they pass. And, if they don't, the ",[46,296,282],{}," job willn't even execute.",[10,299,300,301,306],{},"This structure of the pipeline ensures bugs are never introduced to production.\nAnd hence, ensuring quality standards of the source code. And if you want to\nknow how to set up a code quality check for Python projects, I’ve an article for\nyou. Check out\n",[14,302,305],{"href":303,"rel":304},"https:\u002F\u002Fjarmos.netlify.app\u002Fposts\u002Fa-standard-ci-cd-pipeline-for-python-projects",[18],"A Standard & Complete CI\u002FCD Pipeline for Most Python Projects","\nto know how to set it up.",[10,308,309,310,312],{},"Diving deeper into the ",[46,311,282],{}," job, let's figure out it's exact purpose.",[10,314,315,316,318,319,322,323,325],{},"Like its predecessors, the ",[46,317,282],{}," job also runs on the latest version of\nUbuntu. Then it instructs GitHub to parse the steps for execution. Accordingly,\nGitHub \" ",[140,320,321],{},"copies","\" the contents of the repo in the virtual environment.\nFollowing which the ",[46,324,112],{}," Action is executed.",[10,327,328,329,331,332,335],{},"The ",[46,330,112],{}," Action also requires a couple of variables to function. And\nthese variables are provided using the ",[46,333,334],{},"with"," keyword. The said Action accepts\nmore variables than what we're using. So, do check out it's repo for further\nconfiguration.",[10,337,338,339,342,343,342,346,349,350,287,353,356,357,360,361,363,364,366],{},"That said, we’re using the ",[46,340,341],{},"heroku_api_key",", ",[46,344,345],{},"heroku_app_name",[46,347,348],{},"heroku_email",",\n",[46,351,352],{},"healthcheck",[46,354,355],{},"rollbackonhealthcheckfailed"," variables. Since, the first 3\nvariables accept valuable user-info, they're passed in as GitHub Secrets. The\n",[46,358,359],{},"healtcheck"," variable accepts an URL to the ",[46,362,146],{}," route. And finally,\nthe ",[46,365,355],{}," accepts a boolean value.",[10,368,369,370,373],{},"The last variable helps us as the last-line-of-defence. Anytime an event\ntriggers workflow & the project deploys, a health-check will be performed. It’ll\nlook for a ",[46,371,372],{},"200"," response code & if it fails, the workflow will revert back to a\nprevious working version!",[10,375,376],{},"Pretty nifty if you ask me!",[10,378,379],{},"But then, why do we need such complexity in the first place?",[10,381,382],{},"You see, in production environments it’s common to have robust CI\u002FCD pipeline(s)\nin place. These systems test your commit pushes & PR for any potential\nbreakages. And not to forget coding best practices & quality standards as well.",[10,384,385,386,389],{},"The health-check & roll back features of the pipeline are there as a\nlast-line-of-defence. Since, it is possible for bugs and\u002For breaking changes to\npass the preliminary checks. And it could break our REST API in production.\nImagine what it would be like for your project’s users ( ",[140,387,388],{},"and they paid for\nusing it",")!",[10,391,392],{},"But fortunate for us, you needn’t worry about such a scenario ever becoming a\nreality. With the health-check & roll-back features, issues will turn back north\nas quickly as it turned south. So, in other words, you get a good night’s sleep\nwithout any worry.",[69,394,396,397,342,400,287,403,406],{"id":395},"about-the-procfile-requirementstxt-runtimetxt-files","About the ",[46,398,399],{},"Procfile",[46,401,402],{},"requirements.txt",[46,404,405],{},"runtime.txt"," Files",[10,408,409],{},"As mentioned earlier, Heroku requires certain plain-text files during the build\nprocess. It parses these files to set up the web-server & the dependencies for\nthe project. So, for our REST API project which is a Python application,\nfollowing are the files Heroku needs to parse.",[411,412,413,427,432],"ul",{},[414,415,328,416,418,419,422,423,426],"li",{},[46,417,399],{}," (",[140,420,421],{},"without a file extension",") which Heroku parses to set up a\nweb-server on the remote machine. So, while using uvicorn, the contents of the\nfile would be:\n",[46,424,425],{},"uvicorn main:app --host=0.0.0.0 --port=${PORT:-5000} --workers 4",".",[414,428,328,429,431],{},[46,430,402],{}," lists project dependencies. And, Heroku will parse it\nto install the project's dependencies.",[414,433,328,434,436,437,440,441,445],{},[46,435,405],{}," file states the specific Python version to use for our REST\nAPI. So, if it depends on Python v3.8.10, the contents of the file would be\n",[46,438,439],{},"Python-3.8.10",". ",[442,443,444],"strong",{},"Do note the format"," & it has to be exactly similar else it\nwon't work.",[10,447,448],{},"With these files, your build environment on Heroku should be up & running in no\ntime. But let’s double check the directory structure before committing things to\nversion-control. Here’s what your directory structure should look like:",[10,450,451,452,454],{},"Now, each time you push your changes to a GitHub repository, the push event will\ntrigger the workflow. And if the tests & code quality checks pass, the workflow\nwill try to deploy the project to Heroku. On top of it, if the health-check URL\nreturns a ",[46,453,372],{}," response code back to our workflow, the REST API goes live!",[10,456,457,458,461],{},"You can then navigate to the ",[46,459,460],{},"https:\u002F\u002F\u003CPROJECT-NAME>.herokuapp.com"," URL to check\nout our REST API. If it's working you should see a JSON response on the screen.",[10,463,464],{},"With this setup, now you’ve the best of both worlds. Simple deployment with\nHeroku & robust code quality checks with GitHub Actions! And the best part of it\nall, there was no need to use the Heroku CLI on your local machine. 😆",[69,466,468],{"id":467},"some-potential-roadblocks","Some Potential Roadblocks",[10,470,471,472,474,475,480,483,487],{},"While the techniques & code detailed out in this article works, it’s not robust\nenough. There’re a couple of fragile areas in the ",[46,473,112],{}," Action which\nneed to be taken care of. If you peruse through the source code, you'll find\nit's\n",[14,476,479],{"href":477,"rel":478},"https:\u002F\u002Fgithub.com\u002FAkhileshNS\u002Fheroku-deploy\u002Fblob\u002F79ef2ae4ff9b897010907016b268fd0f88561820\u002Findex.js#L19",[18],"invoking actual",[46,481,482],{},"[git](https:\u002F\u002Fgithub.com\u002FAkhileshNS\u002Fheroku-deploy\u002Fblob\u002F79ef2ae4ff9b897010907016b268fd0f88561820\u002Findex.js#L19)",[14,484,486],{"href":477,"rel":485},[18],"commands","\nusing NodeJS.",[10,489,490],{},"But, NodeJS wasn’t meant to invoke shell commands. So, no wonder things can &\nwill break while using it. In other words, the Action used here is more of a\nworkaround than anything else!",[10,492,493,494,499,500,505],{},"A better solution to this problem would be to wrap an API provided by Heroku to\ncreate an Action. And fortunate for us, appears to be some light at the other\nend of this tunnel. Heroku provides an official API to interact with their build\nprocess & other services. They named it\n",[14,495,498],{"href":496,"rel":497},"https:\u002F\u002Fdevcenter.heroku.com\u002Farticles\u002Fplatform-api-reference",[18],"Heroku Platform API",".\nThey even shared an article to\n",[14,501,504],{"href":502,"rel":503},"https:\u002F\u002Fblog.heroku.com\u002Fprogrammatically_release_code_to_heroku_using_the_platform_api",[18],"programmatically release code to Heroku","\nusing their Platform API.",[10,507,508],{},"So, a shout-out to JavaScript developers. If you’re reading this & you’re\nexperienced developing GitHub Actions, the community needs you. But till then,\nthis article should be a good guideline for anyone wanting to deploy their\nFastAPI app to Heroku.",[10,510,511,512,426],{},"There are other alternatives though. Google Serverless Infrastructure is one &\nother provided by Microsoft Azure and\u002For Amazon Web Service. So, if you want to\ntry them out, check this article I authored Google’s Serverless offering. You\ncan read it at:\n",[14,513,516],{"href":514,"rel":515},"https:\u002F\u002Fjarmos.netlify.app\u002Fposts\u002Fposts\u002Fgoogle-serverless-infrastructure-what-are-the-differences",[18],"Google Serverless Infrastructure: A Primer on GCP & Serverless Computing",[10,518,519],{},"Until then cheers & happy developing! 🍻",[10,521,522,525,532],{},[140,523,524],{},"Originally published at",[14,526,529],{"href":527,"rel":528},"https:\u002F\u002Fjarmos.netlify.app\u002Fposts\u002Fusing-github-actions-to-deploy-a-fastapi-project-to-heroku\u002F",[18],[140,530,531],{},"https:\u002F\u002Fjarmos.netlify.app",[140,533,534],{},"on May 5, 2021.",{"title":536,"searchDepth":537,"depth":537,"links":538},"",2,[539,541,542,543,544,546],{"id":71,"depth":540,"text":72},3,{"id":119,"depth":540,"text":120},{"id":123,"depth":540,"text":124},{"id":185,"depth":540,"text":186},{"id":395,"depth":540,"text":545},"About the Procfile, requirements.txt & runtime.txt Files",{"id":467,"depth":540,"text":468},{"url":548,"alt":5},"https:\u002F\u002Fik.imagekit.io\u002Fjarmos\u002Fusing-github-actions-to-deploy-to-heroku.png?updatedAt=1702974989351","Deploy FastAPI projects to Heroku seamlessly using GitHub Actions. Avoid Heroku CLI and extra dependencies. Configure CI\u002FCD pipeline with GitHub Actions, ensuring quality standards and last-line-of-defense health-checks. Detailed steps, potential roadblocks, and alternatives discussed.","md",{},true,"\u002Fblogs\u002Fusing-github-actions-to-deploy-to-heroku","2021-05-05",{"title":5,"description":549},{"loc":553},"blogs\u002Fusing-github-actions-to-deploy-to-heroku","wx1UKuqEIMXME2P2xll2p5eJSvcbv8Rm0gKCkJf99Bg",1788604450036]