[HN Gopher] The simplicity of single-file Golang deployments
       ___________________________________________________________________
        
       The simplicity of single-file Golang deployments
        
       Author : KingOfCoders
       Score  : 143 points
       Date   : 2023-03-22 13:07 UTC (9 hours ago)
        
 (HTM) web link (www.amazingcto.com)
 (TXT) w3m dump (www.amazingcto.com)
        
       | mastax wrote:
       | > Systemd holding connections and restarting the new binary.
       | 
       | How does this work?
       | 
       | Or does it just mean it stops new connections while it's
       | restarting?
        
         | christophilus wrote:
         | Systemd effectively acts as a proxy. I don't know that it's
         | _actually_ a proxy, but it keeps accepting connections from
         | what I 've seen. I use it for zero-downtime single-binary
         | deploys, and it's great.
        
           | yencabulator wrote:
           | No, it's not a proxy, and it's not accepting connections
           | (unless you're using the inetd emulation, but that's rare and
           | inefficient).
           | 
           | 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.
        
         | hnarn wrote:
         | Could be what's described in this post:
         | https://vincent.bernat.ch/en/blog/2018-systemd-golang-socket...
        
       | sneak wrote:
       | I still put the single file in a docker container because docker
       | isn't complex.
        
         | hsn915 wrote:
         | This makes zero sense.
         | 
         | I don't like cargo culting.
        
           | jerf wrote:
           | There are reasons. It's a reasonable security boundary. It
           | integrates with other things that use Docker as the primary
           | abstraction, and there's good odds I've got other docker
           | things that aren't single binaries like databases and other
           | tools. It puts it into a uniform control interface that works
           | with other things as well. It doesn't cost much additional
           | resources over simply running the binary directly because the
           | real runtime cost of a docker container is the mini-OS they
           | often bring up, not the target executable.
           | 
           | It isn't necessary, but it's not nonsense.
        
           | sneak wrote:
           | I deploy everything everywhere with docker, because then the
           | only thing installed on the system is dockerd. I can deploy
           | identically on different distributions; I don't need to know
           | anything about the host or keep track of files on the host.
           | 
           | 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.
        
       | marcrosoft wrote:
       | How is this news. Welcome to 10 years ago.
        
         | adql wrote:
         | It become easier few years ago as tools to embed files are now
         | nicely builtin into Go instead of external packages.
        
       | danwee wrote:
       | How does one handle zero downtime deployments with single-file
       | golang binaries? I remember I tried this setup some time ago and
       | I couldn't successfully manage cleanly to accomplish no downtime
       | when deploying a new version of my service. The reason was mainly
       | port reuse. I couldn't have the old and the new version of my
       | service running on the same port... so I started to hack together
       | something and it became dirty pretty quickly. I'm talking about
       | deployment of new version of service on the same machine/server
       | as the old version was running.
        
         | bojanz wrote:
         | Socket activation via systemd[0] is an option, assuming you are
         | fine with certain requests taking a longer time to complete (if
         | they arrive while the service is being restarted). Otherwise
         | using a proxy in front of your app is your best bet (which has
         | other benefits too, as you can offload TLS and request
         | logging/instrumentation).
         | 
         | - https://github.com/bojanz/httpx#systemd-setup
        
         | marcosdumay wrote:
         | You can always share ports.
         | 
         | But the one way to do no downtime deployments is to have more
         | than one server.
        
         | SkyPuncher wrote:
         | I've always run services behind a proxy. Spin up a new server
         | with the code (works for any type of deployment). Validate it's
         | up. Switch the proxy from the old to new server.
        
         | klodolph wrote:
         | Some of this is solved by using e.g. systemd, depending on your
         | needs.
         | 
         | > 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.
        
           | joncfoo wrote:
           | > Some of this is probably going to have to be done in your
           | application...
           | 
           | FWICT tableflip does exactly this:
           | https://github.com/cloudflare/tableflip
        
           | mattbillenstein wrote:
           | So can I just start another process with SO_REUSEADDR and
           | gracefully shutdown the old process?
           | 
           | 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.
        
             | klodolph wrote:
             | You may have to start both processes with SO_REUSEADDR, I
             | don't remember the exact semantics.
             | 
             | 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.
             | 
             | https://pkg.go.dev/os/signal@go1.20.2#Notify
        
         | stasmo wrote:
         | This is where the simplicity of single-file golang deployments
         | falls short.
         | 
         | 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.
        
         | iamjackg wrote:
         | If you really don't want to use different ports you can handle
         | it with Docker. Since each container has its own IP, they can
         | all expose the same port. Otherwise, for non-containerized
         | deployments you'll have to resort to two different ports.
         | 
         | 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.
        
         | [deleted]
        
         | adql wrote:
         | Same way you do with any other app not specifically designed
         | for it; you start 2 copies of it and put loadbalancer in front
         | of it. I did that via some systemd voodoo
         | 
         | 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.
        
         | dividuum wrote:
         | Can't help with how to implement this, but just to be sure: You
         | should be able to use the same port in multiple instances if
         | you bind those with SO_REUSEPORT. A quick search points to
         | https://github.com/libp2p/go-reuseport for an implementation.
         | Now you just need a mechanism to drain the old process.
        
           | joncfoo wrote:
           | Rough psuedocode to do this with the built-in http.Server
           | where startServer(..) would use the reuseport library to
           | create the listener so multiple servers can listen within the
           | same process:                 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
           | }       }
        
         | hnarn wrote:
         | This doesn't sound Go-specific, if you use something like
         | haproxy targeting multiple nodes you can take them down one by
         | one to perform a rolling upgrade.
        
         | Edd314159 wrote:
         | I guess this is a problem inherent not just in a single-file go
         | app, but in any deployment where the whole stack is contained
         | within a single process.
         | 
         | 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.
        
           | InitialBP wrote:
           | If you want to do deployments with single-file apps or other
           | "whole stack in a single process" type of deployments there
           | are other options to do it with zero-downtime.
           | 
           | 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.
        
       | andrewfromx wrote:
       | Interesting. Are you saying for a big project run something to
       | minimize the .go files into just one? Assuming there is just one
       | package.
        
         | neverartful wrote:
         | Probably referring to the fact that when you build a go
         | executable there's just a single file to deploy (the
         | executable).
        
           | jjtheblunt wrote:
           | Go has a facility for embedding build time files within the
           | resulting binary such that they can be read as if in a
           | runtime file system, because the Go file access routines know
           | about this file system type.
        
             | PaulHoule wrote:
             | You can definitely pack a Java application into a single
             | JAR file and skip the Docker. Java's xenophobia (allergy to
             | linking libraries) is the real root of "write once run
             | everywhere" so often all you need is the Java runtime.
        
               | thefounder wrote:
               | You still need jre so it's not really a "single file"
               | like on Go
        
               | erichocean wrote:
               | You still need a bespoke systemd configuration for TFA's
               | Go deploys so it's not really a "single file" deploy
               | their either.
               | 
               | 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.
        
               | logistark wrote:
               | You can use JLink to embed only the needed modules and
               | classes of the JRE for your application to run
        
               | creshal wrote:
               | Eh. From an ops perspective there isn't much difference
               | between an executable file that Golang statically
               | compiled all dependencies into and embedded a file system
               | into, and a WAR archive that the Java compiler embedded a
               | file system including dependencies into.
               | 
               | 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 EnterpriseFacto
               | ryPatternFactorySingletonAbstractBaseFactorySingletonProv
               | ider 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.
        
               | PaulHoule wrote:
               | Try Guava or Spring. In either case the framework
               | supplies you with a                  Factory,
               | FactoryFactory, FactoryFactoryFactory, ...
               | 
               | that does the transitive closure so you can just get a
               | "single" object injected into your app where you need it.
        
               | jjtheblunt wrote:
               | "lasciate ogne speranza, voi ch'intrate" (~ lose all
               | hope, who enters) from Dante's Inferno is what comes to
               | mind whenever someone mentions Spring.
        
               | jjtheblunt wrote:
               | class loaders in java are jvm's dynamic linker/loaders,
               | wouldn't you say?
        
       | twic wrote:
       | I feel like i'm taking crazy pills (at a low dose) when i read
       | this stuff.
       | 
       | 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
        
         | simiones wrote:
         | Related to [1], I thought modern Java deployment style is to
         | bundle the required modules of the JDK with your app, rather
         | than any concept of a "deployed JDK".
         | 
         | As it is, the difficulty of deploying a JDK + your app is much
         | more than a single static binary, Go-style.
        
           | 5e92cb50239222b wrote:
           | It depends on your requirements and environment. With most
           | things I am working on the commands to deploy both look
           | exactly the same:                 $ 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.
        
         | lenkite wrote:
         | There are also some advantages towards the tarball jar
         | encapsulating multiple jar approach - some cloud platform Java
         | buildpacks superbly optimize the deployment process by only
         | sending the differential jars - sometimes just the differential
         | classes - which makes deployment 2x-3x faster than Golang
         | single big bang executable approaches.
         | 
         | In our company which leverage both Java microservices and
         | Golang microservices - the Java app deployment is much faster!
        
         | x0x0 wrote:
         | I'm taking the same crazy pills.
         | 
         | 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.
        
         | stanleydrew wrote:
         | Your footnotes basically invalidate your argument. You aren't
         | just deploying a tarball, you also have to deploy the java
         | runtime and make sure it's compatible with your application.
         | 
         | 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.
        
         | smallerfish wrote:
         | We also install the JRE separately, but each app is a single
         | executable (by Java) jar, which stands up a jetty instance when
         | run. We also add a yml for configuration.
         | 
         | It's a much better packaging & deploy story than frontend code
         | or python.
        
         | ardit33 wrote:
         | twic, you just made the op's point.
         | 
         | 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.
        
         | solatic wrote:
         | > Deployment comprises copying the tarball to the server
         | 
         | 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.
        
           | twic wrote:
           | > You must not scale servers up and down very frequently
           | then.
           | 
           | 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.
        
       | drewg123 wrote:
       | The fact that it produces a single static binary is one of the
       | nicest things about golang.
       | 
       | 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.
        
         | synergy20 wrote:
         | very true, for Go though if you need CGO it's hard to make a
         | single executable, otherwise it is great.
         | 
         | 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.
        
           | dilyevsky wrote:
           | > CGO_ENABLED=1 CC=musl-gcc go build --ldflags '-linkmode
           | external -extldflags=-static'
           | 
           | Unless you cant use musl for some reason, but that's a glibc
           | problem, not Go
        
           | aatd86 wrote:
           | Some people are writing alternative via WebAssembly.
           | 
           | For instance wazero which I'm really excited about.
           | 
           | https://tetrate.io/blog/introducing-wazero-from-
           | tetrate/?utm...
        
         | traceroute66 wrote:
         | > The fact that it produces a single static binary is one of
         | the nicest things about golang.
         | 
         |  _AND_ unlike Rust, Go has an eminently usable and
         | comprehensive stdlib.
         | 
         | It means that for most projects you can just Get Stuff Done
         | (TM) instead of deciding which of 24 "reinventing the wheel"
         | Rust Crates you want to use for X, Y and Z functions of your
         | Rust binary.
        
           | tptacek wrote:
           | Please don't start random language fights.
        
             | tomcam wrote:
             | Directly addressing the topic of a upvoted article is
             | starting random language fights?
        
               | doodpants wrote:
               | The article is about Go, and while the article mentions
               | other languages for comparison, Rust is not one of them.
               | So traceroute66's comment comes across as starting a Rust
               | vs. Go fight where not appropriate.
        
               | tomcam wrote:
               | Doesn't come across as starting a fight to me. Not even
               | close.
        
               | [deleted]
        
               | djbusby wrote:
               | Not even, they just expressing an opinion.
        
               | vore wrote:
               | Where does the word Rust appear in the article?
        
             | benatkin wrote:
             | That's no language fight. Rust doesn't want batteries
             | included. That's highlighting a difference between the
             | languages.
        
           | earthling8118 wrote:
           | From another point of view "usable" isn't a word I'd
           | associate with Go, especially not when there is an "unlike
           | Rust" in the phrase. I've not had any issues of the sort when
           | building out Rust applications. At this point the only time I
           | touch Go is if I need to modify something else someone has
           | made-- which I avoid at all costs
        
             | aatd86 wrote:
             | Maybe AI will make it more palatable but I just can't get
             | into Rust.
             | 
             | I can probably easily understand borrowing. It's mostly an
             | issue of controlling pointer aliasing wrt mutability,
             | especially in a multithreaded context I guess.
             | 
             | But that gottdarn syntax... And goroutines are too nice for
             | the type of code I use.
             | 
             | I'm too spoiled with Go I guess.
        
           | pjmlp wrote:
           | If only they would decide what to do with plugin package, not
           | use SCM paths for packages and decide to eventually support
           | enumerations instead of iota dance (Pascal style would be
           | enough.
           | 
           | Maybe 10 more years.
        
             | p5v wrote:
             | Regarding the "iota dance," I wrote my 2 cents on what
             | could be a slightly more robust approach a couple of days
             | ago: https://preslav.me/2023/03/17/create-robust-enums-in-
             | golang/
             | 
             | P.S generic type sets make it even better. I'll write an
             | update to my post these days.
        
             | Joker_vD wrote:
             | type Kind enum {             Simple,             Complex,
             | Emacs,         }              const kindStrings
             | [Kind]string = {             "simple",
             | "complex",             "emacs",         }              func
             | (k Kind) String() string {             return
             | kindStrings[k]         }              func t() {
             | var a = Kind.Emacs - Kind.Simple  // a has type int and
             | value 2             var b = Kind.Simple + Kind.Emacs  //
             | type error             var c = Kind.Simple + 1           //
             | type error             var d = len(Kind)                 //
             | d has type int and value 3                  for k := range
             | Kind {                 fmt.Printf("%v\n", k) // prints what
             | you expect it to do             }                  // not
             | sure about legality or runtime behaviour of the followng
             | var t = Kind.Emacs             t++             t = Kind(42)
             | }
             | 
             | Would be nice, not gonna lie.
        
           | Asdrubalini wrote:
           | Rust's philosophy (unlike Python's, for example) consists in
           | not including tons of stuff in the standard library, this way
           | you can choose the best implementation for your specific use
           | case from one of the many crates (which are super easy to
           | install, by the way). There is no "always the best"
           | implementation for a specific functionality, nor a legacy-
           | but-still-officially-supported-in-std implementation that
           | nobody uses anymore but still needs to be maintained with its
           | own namespace.
           | 
           | I don't see this as negative or "reinventing the wheel".
           | Reinventing the wheel would be writing your own
           | implementation, which doesn't happen if you can choose from
           | many high-quality crates.
        
             | lenkite wrote:
             | "a legacy-but-still-officially-supported-in-std
             | implementation that nobody uses anymore but still needs to
             | be maintained with its own namespace."
             | 
             | The cardinality of the set of "nobody uses anymore" is
             | usually in tens of millions.
        
               | Asdrubalini wrote:
               | If something is used by tens of millions, be sure that it
               | will be updated even if legacy. Just not officially by
               | the people who maintain the language.
        
             | abtinf wrote:
             | > legacy-but-still-officially-supported-in-std
             | implementation
             | 
             | There is _massive_ value in this.
        
               | Asdrubalini wrote:
               | Not officially supported doesn't mean not updated or not
               | supported in general.
        
           | brigadier132 wrote:
           | Using a crate is the opposite if reinventing the wheel. Also
           | are we talking about go? The language without a set
           | implementation?
        
             | asalahli wrote:
             | Parent is saying those crates themselves are reinventing
             | the wheel, not talking about using a crate
        
               | brigadier132 wrote:
               | So by that logic Golang reinvented the wheel by creating
               | a new programming language when java already existed
        
         | 1vuio0pswjnm7 wrote:
         | IME, using musl, compiling static binaries written in C is as
         | easy as it was before glibc changes and as it has always has
         | been on NetBSD. I compile static binaries written in C every
         | day on Linux. I never encountered any problems compiling static
         | binaries on BSD.
        
         | candiddevmike wrote:
         | From what I have seen in major OSS projects like systemd and
         | PostgreSQL, nothing seems to support static linking, to the
         | point where some contributors get annoyed when you ask for it.
         | 
         | 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...
        
           | bzzzt wrote:
           | Packaging would be simpler, but you probably have to update
           | the whole OS and all your installed apps when a security
           | update for a common library is released.
        
             | taeric wrote:
             | Not wrong; at the same time, I'm becoming less convinced
             | this is relevant. Seems many security updates have to do
             | with code paths that are not used in a large number of
             | applications. Similarly, many of them are fixes on features
             | that even more apps didn't want/need, but they got when
             | they updated to get the last round of security fixes. :(
             | 
             | 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.
        
               | nightfly wrote:
               | The problem is having to wait for all those applications
               | to push updates when a vulnerability is found in a common
               | library
        
           | rangerelf wrote:
           | Simpler but small updates, like say openssl, become massive
           | distro updates. There's a reason why everyone went with
           | shared libs when they became stable.
        
             | iambvk wrote:
             | A system with shared libraries also needs an _update_ for
             | the security fixes. There is no avoiding the _update_ step.
             | However, the only difference is the size of the update. If
             | update process is robust, size of the update shouldn 't
             | matter, isn't it?
        
               | dahfizz wrote:
               | It does matter. If there is a problem with openssl, just
               | the openssl maintainers have to push an update and
               | everything on your system is secure.
               | 
               | 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
        
           | rvense wrote:
           | Docker images are static linking for the modern era.
        
         | shp0ngle wrote:
         | You can still do that with musl, can't you? Can make a static c
         | binary with that
        
         | quaintdev wrote:
         | > The fact that it produces a single static binary is one of
         | the nicest things about golang.
         | 
         | Not only that it can also cross compile for different
         | architecture and operating systems.
        
           | lagniappe wrote:
           | I made a cool program for Go projects that will compile all
           | the supported OS and ARCH combos for your code. Please try it
           | :) I use it for everything I make now.
           | 
           | 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
        
           | adql wrote:
           | We used that to make a simple "VPN diagnostics" app for our
           | helpdesk (app checked connectivity and config of the machine
           | then displayed the summary page). Only thing that needed to
           | be written per-os is how to call a browser to display the
           | report
        
             | sconi wrote:
             | interesting. could you share what specifically the report
             | had?
        
       | mirekrusin wrote:
       | We're bundling b/e services from typescript monorepo in
       | production as single bundle files - it works very well. Main
       | reason was simply enforcing lockfile from monorepo.
        
       | neilv wrote:
       | I'm often a fan of single _source_ files, at the package level,
       | including inline embedded API docs and unit tests.
        
       | avgcorrection wrote:
       | > Fast-forward and we were automatically deploying Scala
       | applications from CI bundled in Docker in the startup of my wife.
       | 
       | > Last forward and I have deployed a Golang application to a
       | cloud server.
       | 
       | Editor hello?
        
       | ethicalsmacker wrote:
       | It's not always a static binary, if you use any os/config stdlib
       | function calls or DNS look ups. In that case you need to specify
       | CGO_ENABLED=0 to force static builds.
       | 
       | 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.
        
         | adql wrote:
         | And with a little bit of code you can do switching between "use
         | embedded files/use local files" on the app start easily and
         | have convenience of not having to re-compile app to change some
         | static files.
        
       | lenkite wrote:
       | Is this article from 2016? You can do all this with Java
       | nowadays. I have observed a lot of folks on HN whose last
       | knowledge about Java was from a decade plus ago pontificating
       | about Java deficiencies that no longer exist today.
       | 
       | 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...
        
         | Rapzid wrote:
         | .Net too, also same prob with decade old worldview about .Net
         | 
         | One thing that hasn't changed is that C# > Java ;P
        
       | nathants wrote:
       | (swirls fancy wine)
       | 
       | pairs well with single file frontends.
        
       | masto wrote:
       | When I was stuck doing a web application in Java 15 years ago, I
       | hated everything about it except for the deployment story, which
       | boiled down to a single .war file being pushed to the server.
       | 
       | 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?
        
         | pjc50 wrote:
         | > I don't see what's special or better about compiling
         | everything into a single binary, apart from fetishizing the
         | executable format.
         | 
         | 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?
        
           | brian_cunnie wrote:
           | AWS Lambda is basically cgi-bin. Except, of course, they re-
           | branded it as an exciting new technology, which I think was a
           | clever move on their part.
        
           | mrkeen wrote:
           | What does cgi-bin get you? What does it run on? If I already
           | have a single-file web server, do I need to introduce extra
           | cgi-bin technology also?
        
             | pjc50 wrote:
             | Apache supported it, back in the day before nginix existed.
             | It gives you a single-file web _page_. You can then have
             | multiple pages on the same server run by different users
             | under different userids.
             | 
             | It also operates on a model of one execution run per
             | request. So CGIs that aren't currently being served consume
             | no resources.
        
               | mike_hearn wrote:
               | It still does support it. In fact I use it on our company
               | website to handle the contact form submission. It invokes
               | a Kotlin script which reads the form, handles the
               | recaptcha call and sends an email. Old school but it
               | works and doesn't require any resources except when
               | running.
        
             | giantrobot wrote:
             | The CGI mechanism let the web server call a separate local
             | binary passing parameters of the request as envars/stdin in
             | a specified manner. The "cgi-bin" was just a server side
             | directory where those CGI binaries lived.
             | 
             | 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.
        
             | jacobr1 wrote:
             | For anybody comfortable with the compiling the single-file
             | binary ... it doesn't gain you anything. For a class of
             | "power-end-users" it provides a mechanism to build
             | sandboxed apps on a multi-tenant system. I think the
             | spiritual successors split though between PAAS/heroku-like
             | systems and "low-code" platforms.
        
               | candiodari wrote:
               | It gains you multi-tenancy on the serving end, making
               | infrastructure _very_ cheap.
        
           | tomcam wrote:
           | > Indeed. If you think of the docker image itself as an
           | executable format like PE or ELF, this becomes clearer.
           | 
           | 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.
        
             | crote wrote:
             | A standalone executable doesn't remain a standalone
             | executable for very long, though.
             | 
             | 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.
        
               | candiodari wrote:
               | That used to be apache and mod_* (mod_php, mod_python,
               | mod_ruby, ...)
               | 
               | I've always wondered if it is possible to extend this
               | system. Mod_docker?
        
         | justinsaccount wrote:
         | Have you ever read https://medium.com/@gtrevorjay/you-could-
         | have-invented-conta... ?
         | 
         | You were halfway there :-)
        
         | marcosdumay wrote:
         | > I don't see what's special or better about compiling
         | everything into a single binary, apart from fetishizing the
         | executable format.
         | 
         | 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.
        
           | somehnguy wrote:
           | Exactly - I primarily write Java and fat-jars are great when
           | developing apps for environments I control. But if I want to
           | send an app to a friend it's a few additional steps to make
           | sure they have the correct version of Java, paths are setup
           | correct, etc. This isn't always trivial if they already have
           | a different version of Java and want things to play nice side
           | by side.
           | 
           | 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.
        
             | ihateolives wrote:
             | > But if I want to send an app to a friend it's a few
             | additional steps to make sure they have the correct version
             | of Java, paths are setup correct, etc
             | 
             | 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.
        
             | lenkite wrote:
             | You can use Graal Native Image
             | https://www.graalvm.org/22.0/reference-manual/native-image/
             | to produce a single native executable. Example of a Java
             | Micro-service framework that has first class support for
             | this is Quarkus (https://quarkus.io/). See
             | https://quarkus.io/guides/building-native-image
             | 
             | 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...
        
             | vips7L wrote:
             | > From what I understand Java does have facilities to
             | bundle
             | 
             | jlink, jpackage, and if you need something complex you can
             | use conveyor: https://hydraulic.software/index.html
        
               | mike_hearn wrote:
               | Or even if you need something simple :-) Sending little
               | apps to friends is easy now just like it once was, the
               | difference is that the friends don't need to know what
               | runtime you use. Also not only for Java: Electron,
               | Flutter and anything else works too.
               | 
               | 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.
        
             | pjmlp wrote:
             | For 20 years that AOT compilers for Java exist, even if
             | only available in commercial JDKs at enterprise prices
             | (PTC, Aonix, Aicas, Excelsior, J/Rockit, Websphere RT).
             | 
             | That alternative would be jlink, and GraalVM / OpenJ9 as
             | free beer AOT.
        
           | coldtea wrote:
           | > _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_
           | 
           | 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.
        
           | JCWasmx86 wrote:
           | You could take it a step further and make the user download a
           | small stub Actually Portable Executable
           | (https://justine.lol/ape.html) which downloads the real
           | binaries.
        
           | busterarm wrote:
           | Also if you had some catastrophe where you're no longer able
           | to build overnight or if you have to replace 100% of your
           | infrastructure, you're still able to operate because you have
           | a single compiled binary to ship.
           | 
           | It eliminates whole classes of business risk. The more hosts
           | in your fleet the more risk eliminated as well.
        
           | eddsh1994 wrote:
           | > it cuts the step of installing the correct interpreter...
           | at the cost of requiring the correct computer architecture.
           | 
           | 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.
        
         | adql wrote:
         | Well, system-wise Go app is just a binary that only needs
         | network access, could be run directly from systemd and just
         | have all permissions set there.
         | 
         | 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.
        
           | BrandoElFollito wrote:
           | There is also the deployment part that is easier (au least
           | for an amateur dev such as myself).
           | 
           | 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.
        
       | dicroce wrote:
       | A single binary is nice...
       | 
       | Perhaps second place is using the rpath origin linker option to
       | create a relocatable application.
        
       | RcouF1uZ4gsC wrote:
       | I feel docker in many cases is a hack for languages and runtimes
       | that don't support single file static linked binaries.
       | 
       | Often a single binary is a simpler and better option instead of a
       | docker container.
        
         | mariusmg wrote:
         | Often ?
         | 
         | It's 100% scenario. Complex apps (like Gitea for example)
         | delivered as a single binary is basically the pinnacle of
         | deployment.
        
           | dizhn wrote:
           | gitea, caddy (which can update itself even with the same
           | modules included), restic (again in-place updates), adguard
           | home which embeds a dhcp and dns service etc etc. I really
           | like the stuff the golang developers can put out.
           | 
           | 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.
        
         | ye-olde-sysrq wrote:
         | I more view it as us recognizing that there's more to "a
         | system" than a binary. Kubernetes is this concept taken to its
         | conclusion (since it defines everything in code, literally
         | everything). But docker is often a super convenient middle
         | ground where it's not nearly as stupidly verbose to just get a
         | simple thing running, but still checks a lot of the boxes.
         | 
         | 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.
        
       | [deleted]
        
       | anderspitman wrote:
       | If you're looking for a similar deployment experience, but can't
       | use Golang, we've been using Apptainer (previously Singularity)
       | for a couple years at work. It's really nice to be able to get
       | the benefits of containers while retaining the simplicity of
       | copying and running a single file. Only dependency is installing
       | Apptainer, which is easy as well.
       | 
       | [0]: https://apptainer.org/
        
       | zer00eyz wrote:
       | From the article:
       | 
       | "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.
        
         | ElevenLathe wrote:
         | Anymore? When were we writing "deployable" software in the
         | past?
        
           | zer00eyz wrote:
           | I'll bite!
           | 
           | 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.
        
             | tomcam wrote:
             | I remember those days well. It really meant you had to be
             | careful with bugs and documentation. It is not clear to me
             | that the we are winning with daily or multiple times daily
             | release schedules at this point.
        
               | jrockway wrote:
               | Were people careful with bugs and documentation? I
               | remember the Internet blowing up one day because every
               | Windows install was sending every IP on the Internet a
               | virus, and there was nothing anyone could do about it.
               | (And yes, Unix also had similar worms, though they
               | largely predate me!) Word used to crash and corrupt your
               | entire novel. There was no online banking. I'm not sure
               | the rose-colored glasses are a realistic take on changing
               | software quality.
               | 
               | 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.
        
           | marcosdumay wrote:
           | You used to buy a disk, put it on your computer and run the
           | thing there.
        
             | ElevenLathe wrote:
             | Isn't that just "running" the software? To me, "deployment"
             | implies some repeated process that actually wasn't
             | especially valuable to automate or be very careful about
             | when software was released once a year (and hence the
             | reason it wasn't).
             | 
             | 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.
        
             | xyzzy4747 wrote:
             | My father told me a story about when he was in college, he
             | had to find a book in the library catalogue with the
             | program he wanted, order it and check it out, and then type
             | it up and test/debug it. After that all his colleagues and
             | professors wanted to borrow it as well. This was in the
             | 70s.
        
           | Jtsummers wrote:
           | A better question is what _is_ deployable software? How does
           | it contrast from non-deployable so we can understand what we
           | 're even talking about. Software gets "deployed" all the time
           | so in what way is it currently non-deployable versus some
           | rose tinted view of yesteryear's software?
        
         | [deleted]
        
         | bojanz wrote:
         | _Go isn 't perfect either. The author isn't dealing with assets
         | (images anyone?)._
         | 
         | Go supports embedding assets since 1.16, the author mentions
         | embedfs in the post.
        
           | divan wrote:
           | And people are using go-bindata since Go<1.0, so Go supports
           | embedding assets since forever.
        
         | acatton wrote:
         | > Go isn't perfect either. The author isn't dealing with assets
         | (images anyone?).
         | 
         | 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)."
         | 
         | See https://pkg.go.dev/embed
        
           | zer00eyz wrote:
           | OP here,
           | 
           | 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?
        
             | acatton wrote:
             | I'm not the author of the post, so I can't tell you what
             | the author does.
             | 
             | 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...
        
         | galdor wrote:
         | I'm unsatisfied with the current situation too, but it's a hard
         | problem. You can either go full container (which means no BSD,
         | and having to deal with Docker or Kubernetes and all the
         | associated woes), or fallback to native packages which are a
         | huge PITA to build, deploy and use.
         | 
         | 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.
        
           | xyzzy4747 wrote:
           | Just curious, what's the benefit of using FreeBSD versus
           | Debian/Ubuntu?
        
           | indymike wrote:
           | I'm really growing tired of significant effort development
           | effort going to dealing with deployment on the part of our
           | stack that isn't written in Go. The Go side, deployment is
           | replace binary, restart app, done. The Python and Javascript
           | code we maintain takes significant effort to deploy, and
           | builds can be brittle due to dependency issues.
           | 
           | > 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.
        
         | pjmlp wrote:
         | Java never needed Docker, in fact the Docker / Kubernetes is
         | now re-inventing Java Application Servers with WASM containers.
        
         | Rapzid wrote:
         | > we don't write deployable software any more
         | 
         | 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.
        
         | joncfoo wrote:
         | Assets of any kind can be embedded in the executable and
         | accessed via the embed.FS interface. This makes it trivial to
         | bundle up all dependencies if desired.
        
           | candiodari wrote:
           | Of course Visual Basic 2.0 and Delphi 1.0 both had embeddable
           | filesystems. Even updateable embedded filesystems (which
           | worked because the exe would really be a zip files. Zip files
           | are indexed from the _end_ of the file. So you can prepend
           | the actual executable code and that would work. Zip files are
           | updateable ...)
           | 
           | I believe after a while you also had sqlite-inside-the-exe
           | things.
        
           | cytzol wrote:
           | Embedding your assets like this isn't always an improvement.
           | For example, I work on a site with a Go server and static
           | content pages, and I like that I can update one of the pages
           | and see the change instantly without having to re-compile the
           | entire server binary just to get the new files included.
        
       ___________________________________________________________________
       (page generated 2023-03-22 23:00 UTC)