11 ms·
My deployment platform is a shell script
- dartos 3y agoLimitations are a good thing
- 1vuio0pswjnm7 3y ago"i like things that work for years with as little interaction from me as possible." Shell scripts written in NetBSD sh/Debian ash will work for as long as I live.
- chapterjason 3y agoWhat is existentialcrisis.sh? :D
- tycoon177 3y agoYou don't wanna know
- mnahkies 3y agoI have a very similar system[1] for my personal projects, only I use GitHub actions to push a docker image to ECR and a commit to a config repo bumping the tag. I then have a cronjob to pull the config repo and reconcile using docker compose. I wouldn't use it for serious stuff, but it's been working great for my random personal projects (biggest gap is if something crashes it'll stay crashed until manual intervention currently) - [1] https://github.com/mnahkies/shoe-string-server/pull/2 https://github.com/mnahkies/shoe-string-server/pull/2
- mediumsmart 3y agoMine too, but I am not a repoman. pushthis="rsync -avzh --del ~/path.local/ somedude@path.online:path.online"
- yodaforever 3y agoCan someone ELI5 what he said in the blog post please? What does the script do?
- rovr138 3y agoThe script does a git fetch If the current code is behind (there are new commits), it merges them. If it fails, it stops. If not, it runs `go build`. If it fails, it stops. If not, it moves the binary to the right location and restarts the service.
- bravetraveler 3y agoNot to deride this (too much), but the 'robustness' of deployments with shell scripts is tempting bait. Things are until they aren't, 'nobody rides for free' - decide what you're willing to pay. Example: this interprets the output of 'ls'. Reliability is dependent on good quoting/never introducing a project with spaces Ansible is a nice middle ground, personally. I write the state that differs, use a library of scripting.
- joshka 3y agoOr a project name that overlaps an init.d script they'd prefer to have kept It also doesn't fail immediately if many of the commands fail (instead blindly moves on to the next statement). Consider using bash and these options: https://github.com/kvz/bash3boilerplate/blob/main/main.sh#L18-L27 https://github.com/kvz/bash3boilerplate/blob/main/main.sh#L1... for most scripts.
- cess11 3y agoPutting SAVEIFS=$IFS IFS=$(echo -en "\n\b") or something similar at the top of the script might not come across as comparable to adopting Ansible to some people.
- bravetraveler 3y agoI knew giving specific examples would lead this way, that's not my point. This case has been handled. Others? There isn't anything really insightful here. Someone pleased with a script has not yet grown beyond the needs of it. Yay. The more portable/maintainable version of this is a playbook or whatever. Someone wrote and tested a better version of whatever Work Unit as a module. Wheel enthusiast is pleased with their finely polished, but not particularly traveled, wheel. It's great, but they're commodities and not seeing much. I would have done the same thing with 'ansible-pull' and zero code, in even less time/effort... but I also already know Ansible and bash. Configuration management tools are excellent at managing applications and configs. Who knew. Something else to suggest: systemd timers. At a glance info for scheduling of the job, instead of inferred from logs that may or may not have been recorded. You can also then actually declare your deployment needs networking. Eyeroll.jpg. This is great because the bar is so low. They'll generate a ton of useless logs if they lose networking, as-is. Long enough and the disk will fill: this is trying every minute.
- rcarmo 3y ago(Shameless plug) Mine is a Python script: http://piku.github.io http://piku.github.io
- logro 3y agomy script will never: - go down - require an upgrade - force me to migrate - surprise me - keep me up at night Oh my sweet summer child.
- echelon_musk 3y ago[flagged]
- cess11 3y agoIt's an expression that implies that someone is naive and/or inexperienced.
- echelon_musk 3y agoI find it condescending and cliched in the same way those in this thread do: https://news.ycombinator.com/item?id=39417916 https://news.ycombinator.com/item?id=39417916 It adds absolutely nothing to the discussion. A better response is for the GP to tell us why they think the OP is naive. It's a low effort unsubstantiated jab that pollutes the comments.
- krapp 3y agoYou getting so tilted[0] at a turn of phrase[1] is polluting the comments far more than GP is. Touch grass[2]. [0] "tilted" is a common idiom used to denote anger or frustration which originates from the gaming community, when frustrated pinball machine players would literally tilt the machine. I am not suggesting that you are literally sitting or standing at an incline. [1] "Turn of phrase" denotes a particularly notable form of non-literal expression. It was likely coined by Benjamin Franklin. Ironically, "turn of phrase" is itself a turn of phrase, as it is meant to evoke the image of turning words on a lathe, despite it not being physically possible to turn words on a lathe due to words being abstract concepts. [2] "touch grass" is a lighthearted or humorous way of advising someone to take a break from their online activities, perhaps by going outside and interacting with the real world, particularly if they are excessively immersed in virtual or digital environments. It is also a useful metaphor for partaking in the smoking of marijuana, which is often referred to as "grass."
- lyxell 3y agoRelated: https://github.com/containrrr/watchtower https://github.com/containrrr/watchtower Polls a docker registry and automatically restarts the container with the same flags that it was started with using the latest image.
- philkrylov 3y agoThe heredoc in the script is never terminated. Probably it's not the production version but one for publishing ;-)
- dvfjsdhgfv 3y agoIf the author is reading this: did you code the fish animation CSS manually or used some wrapper for all these moz- and webkit- variants?
- wsintra2022 3y agoIt’s a fish? I thought it was a pocket watch
- dvfjsdhgfv 3y agoWell, that would make it quite an exotic watch: https://j3s.sh/static/unnamed-puffy.png https://j3s.sh/static/unnamed-puffy.png
- anonzzzies 3y agoI use similar things for bigger (multi-server) deploys too. It's light and it just works and works for decades without changes/updates. People say it's brittle; I have a proof of n>0 that this is not the case compared to many other solutions, this post making that point too. Sh/bash/perl(8) have been around forever, they don't break after update etc. I sadly don't recommend it for my day job, simply because of liability. When something messes up with ansible, terraform, docker, cloudformation etc, no-one gets any blame because 'complex systems', 'it happens' etc etc; with a simple script going wrong, they would hang me high even though it probably saves a crap load in maintenance, compute etc over the 10s of years. Same reason we use clusters and IaC while of course nothing we do needs it; if an aws cluster goes down, no-one but aws gets blamed, while if the $2 postgres vps@cheapafhosting (with an higher uptime than that aws cluster by the way; human error downed it a few times, short, but still) is down even for a ping, everyone is upset and pointing fingers.
- cqqxo4zV46cp 3y ago[flagged]
- adamtaylor_13 3y agoIf we’re using the script in the post as an example, that’s hardly a “mess” of shell scripts. I’d rather maintain that than almost any other build system I’ve ever seen.
- mattbuilds 3y agoIt’s funny because in my many years of development I don’t think I’ve ever encounter a “mess of shell scripts” that was difficult to maintain. They were clear, did their job and if they needed to be replaced it was usually simple and straightforward. Can’t say the same for whenever the new abstraction of the day comes along. In my experience what the OP is saying is exactly my experience. The abstractions get picked not because they are best but because they reduce liability.
- from-nibly 3y ago
- gigatexal 3y agoOff topic: I love the writing style of the author and this blog. Gonna follow it.
- zoidb 3y agoNot exactly the same configuration as the op, but if you are developing software using Go, the combination of Caddy, a single go binary, and systemd or some other supervisor is extremely flexible and i think is the way for running multiple services on a single VM. A shell script that deploys a couple config files and off you go. Use different accounts for each service for isolation and put all of your static files in your binary using embed.FS. No need for fancy configuration management or K8s.
- medv 3y agoFor PHP https://deployer.org https://deployer.org For JS https://webpod.dev https://webpod.dev
- TheCapeGreek 3y agoLaravel has Envoy as well: https://laravel.com/docs/11.x/envoy https://laravel.com/docs/11.x/envoy I've played around a bit with Deployer for some projects. It's decent, but feels brittle. It's very dependent on you sticking with its assumed default setup, docs are all over the place, and extending/replacing scripts I found confusing. I moved back to bash scripts.
- pevey 3y agoYou can also use webhooks to deploy with each GitHub push. The advantage over GitHub actions is you don’t have to store any secrets on GitHub or with integrators like Vercel. Just send a payload to your own endpoint each time a commit is made, and that can trigger your shell script to rebuild and deploy. Using symbolic links helps make it more robust to errors. Trigger a pull of the repo, and build. Only if the build is successful, move the symbolic link of your production app to the new build. This also allows keeping some history of builds in case you ever need to troubleshoot.
- chasil 3y agoThe use of ls in this way is not good form: cd /root for project in $(ls go-cicd); I think a better expression would be: for project in ./* do [ -d "$project" ] || continue ...
- adamtaylor_13 3y agoWhy is that not good form?
- GrumpySloth 3y agoIt omits some characters from its output. It also mangles some others through octal escape codes or other such stuff. Depending on flags it will also not handle filenames with spaces or newlines properly. ls output is meant for human consumption, not parsing.
- fellerts 3y agohttps://www.shellcheck.net/wiki/SC2045 https://www.shellcheck.net/wiki/SC2045
- throwaway458864 3y agoI was wondering if Shellcheck warned on this, and of course it does. God I love Shellcheck.
- riddley 3y agoNever, ever parse or rely on the output of ls. It's very unpredictable.
- g4zj 3y agoNot that I agree 100%, but the topic is covered fairly well here: https://mywiki.wooledge.org/ParsingLs https://mywiki.wooledge.org/ParsingLs
- mst 3y agohttps://mywiki.wooledge.org/BashPitfalls#for_f_in_.24.28ls_.2A.mp3.29 https://mywiki.wooledge.org/BashPitfalls#for_f_in_.24.28ls_.... describes the failure modes (and note that it's the very first pitfall because it's a common one - and one I've perpetrated plenty of times myself). Given the situation and the project names, I wouldn't expect the use of `ls` described in TFA to ever be a problem, but doing it with a simple glob would still be nicer and is a good habit to get into overall since then you don't have to ask yourself "is this use of ls going to be safe?"
- OddMerlin 3y agoWhy not use Ansible for something like this? Don’t get me wrong, I love bash scripts like any other old hat, but Ansible scratches this exact itch. You’ve got playbooks that can execute shell, provide logging, better management, history of execution, fleet management, and it’s light weight. And there’s a robust community of shared modules, etc.
- hashar 3y agoWhy add the complexity of having to maintain an Ansible installation, a logging stack, deal with their upgrades and whatever python issue one might encounter. I had the issue of Ansible builtin `shell` not doing the right thing (sh vs bash) or it being unnecessarily slow when uselessly looking up `cowsay`. Adding layers and layers of tooling is often overkill and it is hard to bit the simplicity of 33 lines of shell when the use case is a single person doing the code, deployment and maintenance.
- OddMerlin 3y agoI’m with you on the usecase. Simple server deployment on a VM, bash script is fine, in fact I recommend it. It’s when you start dealing with 5+ VMs that I would start looking into using a tool like Ansible.
- TheCapeGreek 3y agoI think Ansible is a little overkill for some projects tbh. Ideally I'd love a middle ground between bash scripts and Ansible, similar to Caddy's config simplicity over nginx. >it’s light weight Eh, don't think that's the case for everyone. I dabbled with Ansible at a previous job, and set up a very basic personal server setup for Nextcloud and one other app. It was much slower than if I had just written some bash scripts. Idempotency was nice, but the feedback loop wasn't great.
- prmoustache 3y agoI think even ansible is overkill for such a simple thing. Ansible use case works better when you need to do stuff on multiple hosts. For years I've started using and abandoned ansible and puppet recipes for setting up my own computers and everytime the conclusion was that I would spend more time installing git, ansible and puppet in the first place and debugging my recipes than using them. Now all my setup lives in shell functions in my .bashrc.d. I still need git but I don't need ansible or puppet anymore.
- Gys 3y agoI assume this script runs on the server. I was building Go projects on the server as well, a vps where I have several things running. At some point I noticed that larger builds severely effected the other websites. So now I build locally and push the binary to git. To not bloat the project repo with big binary blobs I use a special deploy repo.
- prmoustache 3y agoI think the most important part is the last lines: "consider keeping your little things little. it worked for little old me." The rest are details and every one of us would implement the details in a different way. For example a similar script could be portable with non go projects by looking for a simple build-deploy.sh script that take care of each project deployment mode/instructions.
- TheCapeGreek 3y agoI can't speak to the validity for the author's use case as I'm not a golang dev, but in spirit I do like the idea. I think this trend back to simplicity (monoliths, sqlite, bash scripts) makes it good timing to be posting and learning things like this. Especially as more and more new, easy/low config tools come out like Caddy, this gets simpler over time. I have a testbed boilerplate project for Laravel in which server provisioning & deployments are done by bash scripts over SSH. Excluding comments/spacing, the provisioning script is 35 lines of mostly installing dependencies and minor file template copies. For simpler projects not needing queue workers and/or not using more "exotic" tech like Laravel Octane, this could probably be cut down to 30. TL;DR do the simplest thing that works for you and move on with life - the value in your project, if you intend to deploy it, is for it to be used.
- runlaszlorun 3y agoI’d like to think that there is in fact a trend towards simplicity. But with AI and AI written code, I unfortunately think we may be heading to having even more opaque code.
- throwaway458864 3y agoShell scripts are a more evolved form of programming and nobody can change my mind on that. They require less work, they're easier to make, they're flexible, compatible, composable, portable, small, interpreted, and simple. You can do more with few characters and do complex things without the complexity of types, data structures, locks, scoping, etc. You don't write complex programs in it, but you use complex programs with it, in ways that would be over complicated, buggy and time consuming in a traditional language. That said, it's a tool. Like any tool, it depends how you use it. People who aren't trained on the tool, or don't read the instruction manual, might get injured. I'd like to see a version of it that is safer and retains its utility without getting more complicated, but it would end up less useful in many cases. Maybe that's fine; maybe it needs to be split into multiple tools.
- alanbernstein 3y agoI agree with most of this, my biggest issue is how hard it is for me to recall any moderately complex shell syntax (or the slightly different Makefile syntax). LLMs largely solve that for me.
- throwaway458864 3y agoThe bash man page is my bible. It's dense and long but it always has the answers, you just gotta know where to look.
- nocombination 3y agoWhen people mention `bash` it's immediately a code smell. In order to write portable shell scripts, it must be only POSIX `sh`. If the need ever arises for a more complex data structure, typically I jump into AWK since it's also POSIX compliant. Here's a note from the Ubuntu recommendation: https://wiki.ubuntu.com/DashAsBinSh https://wiki.ubuntu.com/DashAsBinSh
- 10000truths 3y agoI've never been in a situation where I had to care. Bash is everywhere, it's a de facto standard of its own. Even a lot of buildroot and Alpine based Linux deployments, which don't come with bash by default, usually have bash added to them.
- strzibny 3y agoI agree that sometimes Bash is enough, which is what I show in Deployment from Scratch. However I am moving pretty much everything to Kamal now...
- pelagicAustral 3y agoGot your Kamal book. It was much needed, great resource. Thanks for that!
- mst 3y agoGuessing you're talking about https://kamal-deploy.org/ https://kamal-deploy.org/ which looks interesting, though I tend to like reconciliation logic based systems ... but often only fired off imperatively with a plan/apply separation. So I shall be having a poke around anyway :)
- codegeek 3y agoLove stuff like this. For my personal blog, I have a simple Makefile that builds the Go Binary, generates a static HTML output and then deploys it to a DigitalOcean VPS using ssh, reloads Caddy and Supervisor and boom.
- RandomWorker 3y agoYes more you don’t need
- z_zetetic_z 3y agoOr, you could use NixOS and just declare your systems in some text files, git commit; git push. You build script becomes: while true; do git pull nixos-rebuild switch sleep x done That's it. You can even do it remotely and push the new desired state to remote machines (and still build on the target machine, no cross compile required). I've completely removed Ansible as a result and no more python version mismatches, no more hunting endless task yaml syntax, no more "my god ansible is slow" deplyments.
- snippy 3y agoSounds interesting. Let's say the software is a web backend. Can you deploy it like this with zero downtime? So that the new version starts, new traffic goes to it, and the old version handles its active requests to completion and then shuts off.
- z_zetetic_z 3y agoI don't think so, by default I think the nixos process will simply stop (probably by sending SIGINT) the service and then start it again. But if you could have the server into 'lame duck mode' (no new connections accepted, but existing ones can finish) / gracefull shutdown and that's a blocking call (or you could poll if it's still up etc), then you could script that before the 'nixos-rebuild switch' call. Maybe sending SIGINT to the service does that already?
- chasil 3y agoInstead of saying: while true You can instead say: while : There is actually a /bin/true, which could involve the fork of a new process for each iteration of the loop. The form that I have shown you is guaranteed not to fork.
- z_zetetic_z 3y agoThank you sir!
- 3y ago
- danpalmer 3y ago> my script will never: > - go down I've had cron log files get too big and causes issues. > - require an upgrade I can't count the number of times unattended-upgrades has broken something. > - force me to migrate Let's hope the OS is still receiving security updates, because installing on a VPS like this always has a high migration cost. This sort of deployment is a fair starting point, but let's not pretend it's some perfect ideal. .... Look. For deploying a blog, sure, but no one is deploying their blog on k8s. There is a reason why big complex deployment and orchestration systems exist, because there are use-cases for them. This is not one of them, but there's no need to stick your head in the sand over requirements and pretend they don't exist.
- Ingon 3y agoI also started with a simple shell script. Upload sources, build on the target system (golang) and restart the systemd service(es). Then, I needed to make another machine like this, so enter Ansible. This worked well for a long time, and was relatively content with it. Along the way, I leaned about nix (and enough of it) to adopt a simple flake to pull out my tools (like golang, ansible, terraform) through. For a long time, I used it like this (e.g. still ansible, but I started building locally) Finally, I learned enough nix to adopt NixOS. Now, I've converted my project to a nix package and a NixOS module, which allows me to totally describe the state of the machine I want. With this, remote builds and colmena (mostly for pushing secrets), I deploy a complete system, including my own software.
- pfitzsimmons 3y agoAs a pythonista, I am a huge fan of the plumbum library as a replacement for bash. It makes it very straightforward to run a sequence of *nix commands, but you get all the simplicity and power of the python language in terms of loops and functions and so forth. These days, I do all my server management and deployment scripts with python/plumbum. And while simple is great, the necessary features not included in OP's scripts is that I want to spin up the new instance in parallel, verify it is running correctly, and then switch nginx or the load balancer to point to the new server. You are less prone to break production and you get zero downtime deploys.
- cl3misch 3y agoHow do you deal with plumbum not being a builtin module? Do you install it system-wide? This currently holds me back from using sh (the Python lib) for maintaining my servers, especially if I need it with root.
- pfitzsimmons 3y agoThat's not a big deal for me since I only am running a handful of servers. I install it system-wide during initial setup of a new server. Plumbum has the ability to run remote shell commands as well, so I have a script that can login to a new remote machine and do that initial setup.
- tacone 3y agoI use this deploy script for my hobby project: https://gist.github.com/tacone/230d5c305a9c5eff7f58ea2744f2028c https://gist.github.com/tacone/230d5c305a9c5eff7f58ea2744f20... It will connect over ssh, pull the code, build the containers and restart them (scripts/live is just a wrapper around docker-compose). If the build fails, the services will keep running. The only problem I have is that hitting CTRL+C in the very moment the containers are being restarted will leave me with the services down.
- kragen 3y agohere's the deployment script i use most often http://canonical.org/~kragen/sw/dev3.git/hooks/post-update http://canonical.org/~kragen/sw/dev3.git/hooks/post-update #!/bin/sh set -e echo -n 'updating... ' git update-server-info echo 'done. going to dev3' cd /home/kragen/public_html/sw/dev3 echo -n 'pulling... ' env -u GIT_DIR git pull echo -n 'updating... ' env -u GIT_DIR git update-server-info echo 'done.' dev3.git is the origin for dev3, so the `git pull` in there pulls from the bare repo that just got pushed to it doesn't have the 60-second lag and it doesn't load the server all the time. it also doesn't run `go build` or restart a server with openrc, but those would be easy things to add if i wanted them
- anonyfox 3y agoI have a similar deploy.sh script for my go projects, with a slight twist: I compile my Go projects to a binary on a github action, scp it to the server, ssh into it and restart - all done in my deploy.sh and the GHA itself only installs Go and deps (its cached) and then calls that deploy.sh script which sits right in the repo itself. Super happy with it. Speaking as a previous DevOps guy that got sick of AWS complexities.
- fforflo 3y agoScripts get complicated when people start worrying about "portability," like we were in 1991 so we have to use "sh". It's 2024 and you're not developing the next vim or Postgres. Use bash.
- gregsadetsky 3y agoThere's a lot of good in that script - it's just that it doesn't seem to cover functionalities that I'm used to after years of deploying side and "real" (business, etc.) projects to Heroku and Render. How do you manage domain names, who deals with the ssl certificates, how do you set environment variables i.e. "secrets", how can you run postgres, how do you run remote commands i.e. dbmigrate.py, etc. A friend and I have been working for a few months on a project to simplify this - we're not the first to do an open source IaC, but we're scratching our own itch on a lot of features that we've been missing. It's basically "deploy with git push to your own VPS and manage everything with a CLI". I'd love to ask - what do people feel is mostly lacking from OP's script? Which features seem like the most important when deploying/managing a remote server? How do you choose if you're going to use Ansible or K8S or a script, or a full blown IaC i.e. Heroku? Is it price/ownership (i.e. having full control over the machine)/ease of use/speed of deployment/something else? Thanks!
- makz 3y agoI always say: my pipeline is a shell script I call pipeline.sh