The simplicity of single-file Golang deployments
amazingcto.com
amazingcto.com
This used to be easy with C (on BSD & Linux) a long time ago, but then everything started to depend on various shared libs, who then depend on other libs, then things started to even dlopen libs behind your back so they didn't even show up in ldd, etc. Sigh.
Not only that it can also cross compile for different architecture and operating systems.
Just do `release --name "mycoolprogram" --version "0.1.0"`
and it will output all of your labeled release binaries for every platform your code supports.
check it out! [0] You can see it at work here for this simple markdown blog generator I made, which sports 40 different platform combos [1]
[0] - https://github.com/donuts-are-good/release.sh
[1] - https://github.com/donuts-are-good/bearclaw/releases/latest
On the other hand, if you want to cross-compile a C program using GCC, you need to separately build and install a complete gcc+binutils toolchain for every individual arch that you want to target. And you have to handle the dependency management yourself, which may be tricky if your dependencies' build scripts weren't designed with cross-compilation in mind.
Java is an example -- you'll want your your system java deployment to match the stuff stashed in your jar.
Erlang is compiled but usually releases bundle the VM up with the project, so you typically deliver a full directory with the VM packed in there.
Seems like the C/C++ ecosystem will stay dynamically linked, even with a lot of the industry shifting towards statically linked, fat binaries as disk space is pretty cheap.
I wonder how much simpler Linux packaging would be if everything was statically linked...
The docker world is a neat example of this. The stories of ${absurdly high percent} of containers having vulnerabilities only gets to claim that because of "unpatched libraries." If you reduce it to the number of containers with exploitable vulnerabilities, it is still non-zero, but not nearly as high.
I can be confident I will be able to build >90% of go programs by cloning the repo and calling go build.
You are, of course, right that it has the possibility of happening. But by and large, it doesn't happen there any more often than it does in the dynamically linked cases. And, by and large, the dynamically linked cases are often a lot more complicated to deploy. (Granted, deployment for either should be doable nowadays.)
The dream, of course, is you just patch the library and call it a day. The reality seems to usually be a circus check on every application that you have deployed to see if they are impacted anyway.
And "involve" might mean: wait for a bunch of unresponsive external parties to produce a new binary each for a security issue they might not care about. Of course on very different timelines, etc.
If everything is statically linked, you need to wait for every maintainer of every single program on your system to rebuild and push an update. You're basically guaranteed that there is always _something_ missing important patches
I run a few Go apps, all are single executables, upgrading to new releases has never been easier.
if you have a network oriented application, nothing beats golang as far as release|maintenance is concerned.
And there's also "purego" as an implementation that directly generates shellcode.
Maybe those will help you, too?
I am just mentioning these because for my use cases those approaches worked perfectly, CGO free.
Unless you cant use musl for some reason, but that’s a glibc problem, not Go
For instance wazero which I'm really excited about.
https://tetrate.io/blog/introducing-wazero-from-tetrate/?utm...
I've found many times that a go binary, even if it has no cgo or cgo dependencies, will randomly require glibc on the target system to execute if you don't explicitly disable cgo in your build.
When we upgraded to Perl, I liked that system so we designed deployment around "PAR" files in a similar way, bundling all of the dependencies together with the application in the CI build process, and I wrote a tiny bit of infrastructure that essentially moved a symlink to make the new version live.
Google uses MPM and hermetic packaging of configuration with binaries: https://sre.google/sre-book/release-engineering/#packaging-8...
The way I see it, Docker is basically this same thing, generalized to be independent of the language/application/platform. As a practical matter, it still fundamentally has the "one file" nature.
I don't see what's special or better about compiling everything into a single binary, apart from fetishizing the executable format. In any system at scale, you still have to solve for the more important problems of managing the infrastructure. "I can deploy by scping the file from my workstation to the server" is kind of a late 90s throwback, but golang is a 70s throwback, so I guess it fits?
When you distribute your software to other people, it cuts the step of installing the correct interpreter... at the cost of requiring the correct computer architecture.
It is very likely a gain.
Just bundle everything into a native executable, so many little annoyances just disappear. From what I understand Java does have facilities to bundle the runtime now but I haven't had the opportunity to really play with it yet.
For a plain Java app:
"Use Maven to Build a Native Executable from a Java Application" https://www.graalvm.org/22.2/reference-manual/native-image/g...
jlink, jpackage, and if you need something complex you can use conveyor: https://hydraulic.software/index.html
We have an internal version of Conveyor that can be used to push servers as well. It bundles the jvm, makes debs with auto-scanned dependencies, systemd integration is automatically set up, it's easy to run the server with the DynamicUser feature for sandboxing and it uploads/installs the packages for you via ssh. We use it for our own servers. There seems to be more interest these days in running servers without big cloud overheads, so maybe we should launch it? It's hard to know how much demand there is for this sort of thing, though it gets rid of the hassle of manually configuring systemd and copying files around.
My friends would do full stop and reverse at "install Java" step. It will just not fly with 99% of people. It's not 1999 anymore.
That alternative would be jlink, and GraalVM / OpenJ9 as free beer AOT.
Distributing python has to be some of the worst experiences I’ve had in the field.
Not even, as it's trivial to cross compile on Golang. Then you just offer 3-4 arch binaries, and they download the one that matches their platform.
It eliminates whole classes of business risk. The more hosts in your fleet the more risk eliminated as well.
Obviously this depends on the product but I'd give anything to worry about interpreters over the correct computer architecture in the M1/M2 Intel Embedded world.
Indeed. If you think of the docker image itself as an executable format like PE or ELF, this becomes clearer. Rather than targeting the OS API, which has completely the wrong set of security abstractions because it's built around "users", it defines a new API layer.
> "I can deploy by scping the file from my workstation to the server"
I kind of miss cgi-bin. If we're ever to get back to a place where random "power users" can knock up a quick server to meet some computing need they have, easy deployment has to be a big part of that. Can we make it as easy to deploy as to post on Instagram?
But I don’t, because a docker image will not run without docker. A standalone, executable file can be distributed and deployed all by itself. A docker image cannot.
You need something to handle its lifecycle and restart it when it dies. You need something to handle logging. You need something to jail it and prevent it from owning your system when it has a bug. You need something to pass it database credentials. You need something to put a cpu/mem limit on it. Not to mention that most executables aren't standalone but depend on system libraries.
A lot of that can be handled by systemd these days. But now you have a single standalone executable, its mandatory companion config files, and all its dependencies. Docker was designed to create a platform where the only dependency is Docker itself, and it does that job reasonably well.
I've always wondered if it is possible to extend this system. Mod_docker?
If you want to run an executable you have to have some kind of service (in the broad sent) set up to receive it and run it. Same with a docker image. Those services are, if anything, more readily available and standardised for docker than they are for executables.
Whereas lambda will stay running for a period of receiving requests.
I'd be happy with a future that takes some cues from literate programming, where if you want to deploy some application, the way you do it is to upload a copy of the software manual/specification. This should be sufficient to "teach" the server how the application should behave.
It's tempting to say "Ah, sounds like super advanced ChatGPT-ops or something approaching real AGI", but what I have in mind is something decidedly less magical. It's more akin to the sort of thing that Rob Pike brought up in his "The Design of the Go Assembler" talk ("you have a machine readable description[...] why not read it with a machine?").
i.e. a program.
I've actually worked with someone who had a system for compiling the "human readable" side of the h265 specification to executables. This he compared against the "reference implementation" provided in C. As a result he filed a large number of bugs against the standard for cases where the two differed in behavior.
Writing an unambiguous specification is hard work regardless of whether you do it in C or in English or something else, and it's what happens when the surprising cases arise that matters.
If you want to think of it that way (as a way to be dismissive), sure. But I don't know anyone who when asking if some program foo has a manual would accept foo.git as an acceptable answer wrt the spirit of the question.
There's also the not so small matter of packaging/distribution. It's the entire point of the linked post. Stuff like PDF or ebook formats are well understood to be self-contained, which is what makes them something that you can trivially hand-off to someone else with about the same ease as a real book (e.g. attaching it to an email). Software deployments tend to work differently. That should change.
> Writing an unambiguous specification is hard work regardless of whether you do it in C or in English or something else
Right. And programmers already have to contend with this. But when I float this idea around, people like to point this out, as if it's not merely hard, but impracticably hard—to the point of the suggestion being ludicrous. And yet (to repeat myself but not to belabor the point), it's not as if they get to escape this by following current practices; that's something that programmers _already_ have to contend with.
If I understand the GP's point, they like the idea of dropping a singular binary in a directory on a server and then it's magically available as an endpoint off the cgi-bin/ path.
It also operates on a model of one execution run per request. So CGIs that aren't currently being served consume no resources.
Docker is a bunch of file mounts and app running in separate namespaces. So extra daemon, extra layers of complexity. Of course if you're already deploying other docker apps it doesn't really matter, as you'd want to have that one binary in docker container anyway just to manage everything from same place.
I have a CI/CD template, Erin all my web stuff via a dockerized caddy reverse proxy, do not need to touch the configuration of the host (to create .service &co. files)
I find deploying to docker just simpler.
You were halfway there :-)
"Standing here it looks like Docker was invented to manage dependencies for Python, Javascript and Java. It looks strange from a platform that deploys as one single binary."
Let me say the quiet part out loud: Docker is covering up the fact that we don't write deployable software any more.
Go isn't perfect either. The author isn't dealing with assets (images anyone?).
I think there is plenty of room for innovation here, and were over due for some change.
From the article: "The Go web application had all files like configurations (no credentials), static css and html templates embedded with embedfs (and proxied through a CDN)."
How are you doing caching without a mod time?
https://github.com/golang/go/issues/44854
Are you re-naming or hashing for cache clearing?
What I do in my projects is that I tell varnish-cache to cache assets in "/static/..." forever. And I have a "curl -X PURGE <varnish_endpoint>" as part of the "ExecStartPre=" of my go binary.
• https://varnish-cache.org/docs/trunk/users-guide/purging.htm...
• https://www.freedesktop.org/software/systemd/man/systemd.ser...
Of course, at the end it's a question of preference. People might disagree.
Go supports embedding assets since 1.16, the author mentions embedfs in the post.
I believe after a while you also had sqlite-inside-the-exe things.
What did it use to look like exactly, this "deployable" software? Going back to the birth of web 2.0 we had Perl, PHP, Java(?), .Net Framework a few years later. These all required tons of pre-configured infrastructure on the servers to run..
> It looks strange from a platform that deploys as one single binary
It's just a tool with many uses. I CAN deploy my Asp.Net app as a self-contained(even single) file.. But the size of updates is smaller between images if I copy the app into an image that already has the .Net and Asp.Net library code in the base layers.
I think that Nix and Guix have part of the solution: have a way to build fully independent packages that can be easily installed. But I'm not comfortable with the complexity of Nix, and Guix does not run on FreeBSD. And ultimately you still have to handle distribution and configuration of the base system you deploy on.
Innovation is possible, but there are a lot of expectations for any system dealing with building and deploying software. I feel that there are fundamental limitations inherited from the way UNIX OS work, and I wish we had lower level operating systems focused on executing services on multiple machines in a way similar to how mainframes work. One can dream.
> I feel that there are fundamental limitations inherited from the way UNIX OS work
There are, but the way go does deployments plays to Unix's strengths.
We used to ship software, on discs, and we didn't have the Internet to update it.
There is plenty of software out there that works via your linux distress package manager. Note that this isn't everything you can get from your package manager. Plenty of things that are available are broken or miersable to get working unless you get the container/vm version.
Today, the tools are available to move quickly and maintain quality. You probably do what was months of manual testing every time you save a file, and certainly every time you commit a set of changes. There are fuzz testers to find the craziest bugs that no human could even imagine. There are robots that read your PR and point out common errors. HN really likes to pan on software quality, but "I don't like this feature" is not a bug per se, just a company you don't like. There are a lot of those, but there are more lines of code than ever, and a lot more stuff works than 30 years ago. I think we, as a field, are getting better.
Also, disks are a horrible way to deploy software. They have all the same problems of just distributing a random tarball: What operating system is it for? What version? Where do I copy the files? How do I get the OS to automatically start the service on startup? What version of make does it use? How about which libc and cc? You can say this stuff in the README (or printed docs) but isn't something more "deployable" when it's all machine-readable and can be reasoned about automatically? This is what package managers were invented for.
We do. It's just software now is a container image, and Docker is a development tool like Make.
I deploy Java applications. In a runnable condition, they aren't a single file, but they aren't many - maybe a dozen jars plus some scripts. Our build process puts all that in a tarball. Deployment comprises copying the tarball to the server, then unpacking it [1].
That is one step more than deploying a single binary, but it's a trivial step, and both steps are done by a release script, so there is a single user-visible step.
The additional pain associated with deploying a tarball rather than a single binary is negligible. It simply is not worth worrying about [2].
But Go enjoyers make such a big deal of this single binary! What am i missing?
Now, this post does talk about Docker. If you use Docker to deploy, then yes, that is more of a headache. But Docker is not the only alternative to a single binary! You can just deploy a tarball!
[1] We do deploy the JDK separately. We have a script which takes a local path to a JDK tarball and a hostname, and installs the JDK in the right place on the target machine. This is a bit caveman, and it might be better to use something like Ansible, or make custom OS packages for specific JDKs, or even use something like asdf. But we don't need to deploy JDKs very often, so the script works for us.
[2] Although if you insist, it's pretty easy to make a self-expanding-and-running zip, so you could have a single file if you really want: https://github.com/vmware-archive/executable-dist-plugin
I agree that Go fans make too much of the single binary feature, but it does seem easier than your deployment process.
Of course your process is easy for you because you built it to fit your needs. But if you imagine a new developer who has no experience deploying either Java or Go applications, and consider what's easier to deploy without any previous knowledge or automation, I think you might agree the Go deployment options are simpler.
That’s not much of a problem in practice though. The JDK is just a tarball as well. You can even combine it with your application tarball into one! (With the drawback that now you have to create one combined tarball per target platform.)
In our company which leverage both Java microservices and Golang microservices - the Java app deployment is much faster!
You must not scale servers up and down very frequently then.
> both steps are done by a release script, so there is a single user-visible step... we do deploy the JDK separately
Wait, so it's not really a single user-visible step. You have one user-visible step to deploy the application server, and a different user-visible step to deploy the JDK.
Look, there's a reason why this way is old-fashioned. If you bought the server outright (i.e. running on-prem/colo), and so it represents a sunk cost, and the usage is all well within the ceiling of what that server is capable of providing, then sure, that's an eminently reasonable setup. If that server is humming along for several years, and electricity/data center costs are cheap, you're probably even saving money.
But in most cloud-first architectures, if you're not scaling down on low-usage times, you're wasting money. Scaling up and down is much, much simpler with immutable infrastructure patterns, and it's much simpler to just replace the entire image - whether that's a VM, or a container, or something else - rather than replacing just the application.
Indeed we don't. But i don't see why it would be a problem if we did. If you can run a script to copy a Go binary to a VM when you scale up, you can run a script to copy a tarball and unpack it. If you're scaling based on a prepared image, then you can prepare the image by unpacking a tarball, rather than copying in one file.
> Wait, so it's not really a single user-visible step. You have one user-visible step to deploy the application server, and a different user-visible step to deploy the JDK.
Oh come on! If that matters to you, change the app deployment script to run the JDK deployment script first. Bam, one step.
> Scaling up and down is much, much simpler with immutable infrastructure patterns
Sure, and as far as i can see, this is completely orthogonal to having a single-file deployment. You haven't made any case at all for why having single-file deployment is valuable here.
If you have a prepared image / immutable infrastructure pattern, that image is the single-file pattern. Container images are tarballs. Go isn't actually all that special here, if you compare apples to apples in containerized deployments: either you bake the standard library into the binary (which Go does), or you bake the standard library into the tarball (which the JDK forces you to do). Either way it's a single file.
We did this deployment pattern with a jar in, like... the early 2000s? It's trivial (well, maybe an annoying couple hours to config, but it's a config once and then done) in maven to build a megajar and add every single thing you need into one large jar. All resources, dependencies, etc.
And then deployment is, indeed, an rsync.
As it is, the difficulty of deploying a JDK + your app is much more than a single static binary, Go-style.
$ rsync -a foo server:/opt/foo
$ rsync -a bar server:/opt/bar
Can you guess which one is a static binary written in Go, and which one is a directory with a slimmed down JRE produced by jlink + an application jar?For those using containers, there's no practical difference between the two.
It's a much better packaging & deploy story than frontend code or python.
I too miss the days where you can just ssh/ftp a file, and boom, it was live. (this was usually a php file back then).
It is such a great feeling to be able to know whats going on at every step. With increased complexity, so has the deployment process in general. The java steps you described where the beginning of more complex deployments back then (1999-2001)
And, yes, I agree with the author in this case. Golang, makes it super simple to deploy a web service.
Can that be said about your program with other people?
But the original post we're discussing, and my comment on it, was about deploying to servers.
But I must confess,
> Systemd also restarts the app daily to make sure it works properly long term
leaves me with a viscerally negative feeling. I feel like daemons should be able to run for years unless there is some kind of leak. Maybe I am wrong.
And I’m not taking about “making a static binary in $LANG is easy, just follow these 7 steps…” trust me it’s nothing like Go then.
> I couldn't have the old and the new version of my service running on the same port...
You can, actually! You just can’t open the port twice by default. So one or both of the processes needs to inherit the port from a parent process, get passed the port over a socket (Unix sockets can transmit file descriptors), or use SO_REUSEADDR.
There are some libraries that abstract this, and some of this is provided by tools like systemd.
Some of this is probably going to have to be done in your application—like, once your new version starts, the old version should stop accepting new connections and finish the requests it has already started.
FWICT tableflip does exactly this: https://github.com/cloudflare/tableflip
The master/worker thing that nginx / gunicorn et al do is pretty neat, but relies on signals; so seems pretty messy and error prone to write yourself.
People have a healthy skepticism of signals from the C days, but if we’re talking about Go, you’d just call signal.Notify. Any way of signaling your app to shut down works, though.
But TECHNICALLY to do that in one without external proxy you'd need to figure out how to set SO_REUSEPORT for the web socket handler, then start the second one before the first.
Haven't actually tried it but someone apparently did: https://iximiuz.com/en/posts/go-net-http-setsockopt-example/
You'd still have any ongoing connections cut unless you unbind socket and then finish any existing connection, which would be pretty hard with default http server.
I just put HAProxy instance on my VPS that does all of that, including only allowing traffic once app says "yes I am ok" in healthcheck. Then the app can have "shutting down" phase, where it reports "I am down" on healthcheck but still finished any active connections to the client.
In either case, you will need a reverse proxy like Traefik/Nginx in front to smartly "balance" incoming requests to the two instances of the service.
The post says the process starts up quick enough that the process being temporarily unavailable isn't noticeable - but what if the process _doesn't come back_? It's also impossible to do blue/green deployments this way.
It's clearly not a solution suitable to large-scale deployments. The simplicity has its trade-offs.
One good option would be to spin up a second server/instance/container, run binary on new system, ensure it's good, once comfortable then swap DNS entry to the new system.
func reloadConfig(config) {
if newServer, err := startServer(config); err != nil {
// gracefully shutdown previous server
// no new connections will go to old server
oldServer.Shutdown(...)
oldServer = newServer
}
}If you want stateful zero downtime deployments, use Elixir or Erlang that has the ability to live migrate data from one version of code to the next.
But the one way to do no downtime deployments is to have more than one server.
Just make sure you’re not slowly recreating bad, homebrew versions of all of the nice things that Kubernetes does in an attempt to turn a simple deployment into a production ready deployment.
Often a single binary is a simpler and better option instead of a docker container.
I used to feel similarly with Java. "Why," I asked, "would you need this docker thing? Just build the shaded JAR and off you go."
And to be sure, there are some systems - especially the kind people seem to build in go (network-only APIs that never touch the fs and use few libraries) - that do not need much more than their binary to work. But what of systems that call other CLI utilities? What of systems that create data locally that you'd like to scoot around or back up?
Eventually nearly every system grows at least a few weird little things you need to do to set it up and make it comfy. Docker accommodates that.
I do think there's a big kernel of truth to your sentiment though - I loved rails as a framework but hated, just hated deploying it, especially if you wanted 2 sites to share a linux box. Maybe I was just bad at it but it was really easy to break BOTH sites. Docker has totally solved this problem. Same for python stuff.
I do think docker is also useful as a way to make deploying ~anything all look exactly the same. "Pull image, run container with these args". I actually think this is what I like the most about it - I wrote my own thing with the python docker SDK, basically a shitty puppet/ansible, except it's shitty in the exact way I want it to be. And this has been the best side effect - I pay very little in resource overhead and suddenly now all my software uses the exact same deployment system.
It's 100% scenario. Complex apps (like Gitea for example) delivered as a single binary is basically the pinnacle of deployment.
I even asked someone to produce a fresbsd binary please and they added one line to their github ci to make it available that day.
Worked well for me for a zero-install bridge app.
How does this work?
Or does it just mean it stops new connections while it's restarting?
It's merely passing the listening socket as an already-open file descriptor to the spawned process.
The "keeps accepting" part is just the listening socket backlog.
Last I looked, systemd socket passing couldn't be used to do graceful shutdown, serving existing connections with the old version while having the new version receive new connections. Outside of that, it's very nice.
I have been doing single binary full website deploys for about ~16 months in production. That includes all html, css and js embedded. It has been wonderful.
Use the GraalVM native build tools https://graalvm.github.io/native-build-tools/latest/index.ht....
"Use Maven to Build a Native Executable from a Java Application"
https://www.graalvm.org/22.2/reference-manual/native-image/g...
Can you compare doing the same thing with Java? How many more steps does it take, assuming that you don't already have a standard Java dev setup that's configured and ready-to-go on your machine? Now, let's let you put your thumb on the scale and assume that you do already the standard setup, but you want to pursue what this posts lays out and that you insist can be done with Java. How much more effort does it take just to go from "typical Java setup" to "setup that actually lets you do what this article describes"? If the answer is not zero but your position is still that there's nothing special here because "You can do all this with Java nowadays", then it's because you're not understanding what "here" and "this" actually are.
5-6 minutes extra. 3 more steps. You are making a mountain out of a mole-hill. Sorry to burst your bias-bubble, but Java deployment is damn easy nowadays.
If you are leveraging a cloud build-pack, single file native exe deployments like Go's are un-optimal since they are slower - no intelligence to perform differential updates like the way you get for a Java fat-jar or tar-ball.
That's greater than zero. So you're already failing—and that's on top of the handicap already afforded to you for the initial setup.
(Even ignoring that, I'm suspicious of your numbers. Have you actually measured it? Do you have something to show that your off-the-cuff figures match what people will actually experience?)
> Sorry to burst your bias-bubble
Major irony—assuming bias on my part (where there is, in fact, none) without realizing that doing so broadcasts evidence of yours.
What ? This is the time required to download GraalVM and then configure your project. This is a ONE-TIME setup. Strictly speaking, if you omit a build tool like maven - you don't really need it for a native binary - then it is simply one additional install - get Graal native image and compile your code to a binary using the CLI. Why would I even bother measuring this ?
The fact that you are even asking for "measurements" without providing corresponding "measurements" for the Golang setup - something the article never even bothered to mention is ludicrous. Why would one even consider the one-time cost of installing one additional tool ?
That way lies silliness - I should then consider Java superior because Go requires additional command to install godoc for example while Javadoc comes with the base JDK.
> Major irony—assuming bias on my part (where there is, in fact, none) without realizing that doing so broadcasts evidence of yours.
There is nothing ironic in pointing out your double-standards. I develop in both Go and Java. Both languages come with their advantages. The strict advantages Go has over Java is goroutines, more feature-packed stdlib and reduced memory footprint at runtime. (The goroutine advantage has also gone away now with virtual threads in Java).
But single file deployment is NOT an advantage Go holds over Java - since several years now.
> The fact that you are even asking for "measurements" without providing corresponding "measurements" for the Golang setup
I'm responding to your claim. The onus is on you to substantiate it.
> Why would one even consider the one-time cost of installing one additional tool ?
Aside from the low-hassle relative simplicity being the fundamental subject of the submitted article, there's no reason I suppose.
> There is nothing ironic in pointing out your double-standards.
There is no double-standard aside from the aforementioned handicap that benefits you, and you're moving the goalposts, besides. You specifically accused me of being in a "bias-bubble". There is no purer form of irony.
Snort. I believe the original claim has been substantiated enough already - that Go holds no advantage over Java wrt single file deployment as you can achieve "single binary" in Java too if you wish.
Measurement of a one-time setup cost was a claim made by you, not by me. You gave a description and I gave a description. Demanding precise time measurement of a ballpark is where the "double standard" lies - since you never provided any to "substantiate" yours, yet demand one from me by statements like: "So have you measured it or not?" for a CLI compiler install.
But, hey, in the spirit of goodness:
time bash <(curl -sL https://get.graalvm.org/jdk)
8.62s user 3.90s system 29% cpu 42.876 total
export JAVA_HOME="graalvm-ce-java17-22.3.1/Contents/Home"
export PATH="$JAVA_HOME/bin:$PATH"
javac HelloWorld.java && native-image HelloWorld
<....compiler output removed, except for last line>
Finished generating 'helloworld' in 16.8s.
./helloworld
Hello, World!
Huh, so it was actually FASTER than I thought for an end to end setup. You don't even need the open JDK since graalvm already comes with it. (I mistakenly thought needing open jdk was a pre-requisite).So, its literally just: install tool, set path and invoke compilation commands. 1-2 min end to end.
> write a hello-world service, and run "go build" [...] you'll have your single-file binary ready for deployment
You seem to have written a much simpler hello-world toy program (one that literally just prints those words and exits), instead of a "hello-world service [...] ready for deployment". (Am I mistaken? 16 seconds—on what I'm assuming is not a modestly specced workstation—is a crazy-long compile time for a simple hello-world program, but it would be pretty crazy even for a deployable service.) What happens if you attempt to satisfy the actual criteria laid out?
> I believe the original claim has been substantiated enough already
Uh, no. It's not substantiated until someone substantiates it. To "substantiate" something is not synonymous with merely claiming that it is true.
> Demanding precise time measurement of a ballpark is where the "double standard"
It's not a double standard; I didn't even ask for "precise time measurement". I asked you to substantiate what you're saying. Seeking substantiation does not comprise a separate claim in and of itself (but nice try, I guess?).
"Building is a resource hog. I set up a VM with 2 CPUs, 100GB of disk space, and 8GB of memory, and even a relatively small project took over 10 minutes to build." [3]
"the power of the Java ecosystem is libraries, but you cannot use 99% of them because they just use too much reflection and I am afraid they will never be prepared for Spring AOT" [3]
"DI does not work inside a native binary at runtime, you need some tool which does the whole DI at compile time (Spring Native and Quarkus do that)" [1]
"I do not even think we are in the alpha stage. For example, for 2 days I am fighting with a simple microservice using JPA, MySQL, some transactions and without success. Fixed at least 4 bugs, and now I gave up. I cannot imagine what problems can arise in mid-size projects." [3]
"GraalVM [not] supporting Swing and JavaFX "out of the box"." [4]
"Even assuming your app run as intended (which is already complicated enough, you will have to run their java agent to register all reflections), there is no telling how it will perform. For example record methods are currently implemented using reflection which completely obliterate performance: https://github.com/oracle/graal/issues/4348" [1]
This is my pet peeve. Presumably you're very familiar with the single binary Java deployment story you're describing. And yet, somehow everything you say is false. It's incredibly vexing to have to go out and verify such claims about subject matters that I'm not that familiar with because the supposed experts are basically lying through their teeth :/
[1]: https://www.reddit.com/r/java/comments/vc9s3u/what_is_your_e...
[2]: https://www.reddit.com/r/java/comments/e9m51x/whats_your_opi...
[3]: https://www.reddit.com/r/java/comments/10cv886/personal_expe...
[4]: https://www.reddit.com/r/java/comments/wodoy4/if_you_were_th...
One thing that hasn't changed is that C# > Java ;P
> Last forward and I have deployed a Golang application to a cloud server.
Editor hello?
pairs well with single file frontends.
Perhaps second place is using the rpath origin linker option to create a relocatable application.
Encapsulate functionality and break it into logical containers.
That's what modules are.
That's what files are.
That's what functions are.
That's what software engineering is.
I don't like cargo culting.
It isn't necessary, but it's not nonsense.
I can keep all of my build artifacts in a docker image repository with versions. I can deploy any version on any host without worrying about copying the version to the host.
Whether your deploy is 1000000 files or 1, this system has clear advantages to copying things to the base server OS and turning it into a snowflake.
And like the sibling comment noted, once you're allowed to set up the machine to support easy depolyment (e.g. JRE, Tomcat), a WAR becomes a single file deploy.
Both are self-contained single files you can give to a completely different organization and expect to run on the first attempt with no complications.
It's just that the latter needs Tomcat (not an issue, realistically) and has to be written as EnterpriseFactoryPatternFactorySingletonAbstractBaseFactorySingletonProvider that makes you feel dead inside just from looking at the documentation; while Golang (and similar newer languages) give you a lot more flexibility and better ergonomics on the developer side.
Factory, FactoryFactory, FactoryFactoryFactory, ...
that does the transitive closure so you can just get a "single" object injected into your app where you need it.