Friday, March 12, 2021

Why We Don’t Use Docker (We Don’t Need It)



UPDATE: minor edits to mention that we do have a dedicated build server after this got posted to reddit.


This might end up getting a lot of hate.  In other jobs, we’ve used docker and it’s worked out just fine (for the most part… there was that time the RedHat filesystem on our prod server got mysteriously hosed -- maybe it wasn’t docker’s fault.)  But no, the reason we don’t use docker is because we don’t need it. Literally.  Writing golang web services and static html embedded with with golang 1.16’s new //embed directive, we end up with a single deployable binary.

As a self-sustaining startup, we have limited resources to devote to tasks.  We chose golang exactly for this reason.  It sure would be *nice* if we could spend a couple weeks building the perfect CI/CD pipeline, an elegant deployment process, along with pretty dashboards.  But we have software we need to ship in order to get users in order to drive subscriptions.  Anything that doesn’t directly serve that goal is a complication.  So at best, docker is a complication.  A 9 million LoC complication that brings its own bugs and its own idiosyncrasie

Golang allows us to build web services that are insanely fast (at least for the level of growth we are currently at) and insanely scalable (again, for the level of growth we are currently at).  We can handle thousands of transactions per second on a single server.  We don’t have anywhere close to a thousand transactions per second.  But, sure, we could have probably done the same with node or deno.  The V8 engine is also insanely fast.  When high traffic for your app is probably 2 transactions per second (we have a workout video app – we don’t have the same scalability problems as Twitter), it really doesn’t matter what programming language you choose.  If you run out of capacity, upgrade your single server.

No, the reason we went with go was because golang’s packaging is *so* much better than node.  Or java.  Or C#.  We get a single binary. 

Building is as easy as:

    go build

Testing is as easy as

     go test

Deploying the app is as easy as:

     scp app user@host:
     ssh user@host “nohup ./app”  

Note, we did get a *little* more complicated and create a SystemD script to launch it at startup.  We also made sure we had a dedicated build server, which essentially runs a 10 line shell script that does all the build goodness needed (git clone, go build, go test, go lint, go vet)  But there were those of us arguing that even that was too complicated.  At some point we may even add a UI like https://www.rundeck.com/ to control deploys.

Total time to create our build and deploy system was so small we don’t even know how to measure it.

Now, think about how much time you’ve spent learning docker. Deploying docker. Troubleshooting docker.  Even if you love it, it changed your life, it’s better than sliced bread and it solves world hunger… in the end can you really say it’s simpler than what we’ve got for free built in to golang?  I guarantee you it’s not.

Please comment below if you like these thoughts.  And while you're at it, you could give our app a try as well: https://meezeeworkouts.com/getapp

 



 

 

 

 

31 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. You can also try this tool which does more cool stuff.

    https://github.com/geosoft1/tools/blob/master/tools/cloud

    Documentation:
    https://github.com/geosoft1/tools/wiki/Cloud-tool

    ReplyDelete
  3. How about your database? Do you pass in the DSN string via a CLI flag or something?

    ReplyDelete
    Replies
    1. Great question. We specifically made choices to avoid needing a database (yet). State is the root of all evil. ;)

      Delete
    2. Do you just write files directly to disk or store things in memory?

      Delete
    3. Good question. We made a conscious decision (for now) to store data client side. So, yes, in files. To be precise, everything gets stored using @AppStorage annotation for swiftUI.

      Delete
    4. SQLite could be an option for lightweight, server-side storage. No need for a fancy DB engine–just let the main app process manage the database directly.

      Delete
    5. Thanks for the suggestion. We've tried hard to be stateless with our web services so we can fail over to our DB server or deploy a new one quickly. Having state on the server without high availability (like with SQLLite) is something we want to avoid. But agree that would make it simple.

      Delete
  4. Thx for sharing, did you consider serverless instead of running your own servers ?

    ReplyDelete
    Replies
    1. Great question. Yes. That's absolutely on our radar. We spent an afternoon performance testing and when we realized we could do 1500 tps with a single server, we decided we were good (for now). But, we've kept on eye on being able to deploy serverless in the future.

      Delete
  5. Would this approach be OK if you were using a python/Django system??

    ReplyDelete
    Replies
    1. Possibly. Golang makes it easy by having a single static binary. Once you get into python + django you've got a lot of dependencies to manage. You *could* zip them all up and deploy. Or you could use docker to package them up in a standard way. Fun arguments about which is better.

      Delete
  6. This comment has been removed by the author.

    ReplyDelete
  7. Yes, I agree that statically linked golang is pleasant to deploy. Do you have or intend to produce an Android app for MeeZee Workouts? I'm not a fan of embedding HTML though, that smells like PHP from the bad old days.

    ReplyDelete
    Replies
    1. I figure it's because golang executables are statically linked that you don't need to use docker. You could deploy more than one file.

      Delete
    2. Great question. We'd love to have enough interest to make an android version. It wouldn't be terribly hard since we consciously put a lot of our logic in web services. And most of us already have java experience. More of our thinking about why we went SwiftUI only is here: https://launchyourapp.meezeeworkouts.com/2021/02/intro-to-launch-your-app-at-meezeeco-we.html

      Delete
  8. This is definitely the kind of company I want to work at. Arguments about 10 lines being too complex and faster deploys than everyone else for the win.

    ReplyDelete
  9. Have you considered Rust (also has nice packaging, I would argue is "safer" in the theoretical programming language theory sort of thing and compiles to a single binary – which pretty much any compiled language does, no, unless you use static linking)?

    Congratulations on reducing complexity :D

    ReplyDelete
  10. Thanks! Considered Rust, but enjoying golang too much. Different strokes for different folks. It's amazing how few languages still compile to a single static binary. We came from a java world where you get a single binary... except for the need for a JVM, external jars (sometimes) and potentially an app server. But yeah... generally speaking, we're all for any language that does produce a single binary. And thanks!

    ReplyDelete
  11. Yes, this works nicely. I used to have a similar setup but my failed on DB, on non-optimized SQL queries specifically. Then I decided to go to the cloud - Docker, Kubernetes, Terraform ... year of work. But in reality I should have fixed those queries and buy faster bare metal server.

    ReplyDelete
  12. Dart has the same enjoyable compile to single (native code) static binary functionality too. It also have nice support for no-shared-memory actors (Isolates) which you may enjoy too.

    ReplyDelete
    Replies
    1. Thanks for the suggestion. Been meaning to check Dart out. Been slightly afraid it's bound for the standard google treatment of hype it for 5 years and then pull all support.

      Delete
  13. Great article! I've also been using the same approach and really like it! Here is one example:

    https://github.com/simpleiot/simpleiot

    This approach also works really well for edge device software (Simple IoT is actually designed to target both cloud and edge applications). Everything is statically linked into one binary, so updating a device in the field is as simple as pushing out a new app binary to the device.

    I've also been looking into Rust some and find it interesting. However, I've found Go to be very reliable in practice -- I'm not 100% sure why as it does not have all the formal guarantees of languages like Rust, but I think having garbage collection, the practice of returning errors, and keeping things simple gets you most of the way there. Likewise, I'm having fun with Go and find the in pleasant to program in.

    If you are ever looking for a pure Go embedded database (something like sqlite), I've been using https://genji.dev -- has been working out well.

    ReplyDelete
    Replies
    1. Good tip. We are also fans of cockroachdb, which until recently was pure golang as well. Not embedded -- the opposite, horizontally scalable multi-master.

      Delete
    2. If you like cockroachdb, you may also like Bedrockdb, a replication cluster layer built on top of SQLite. Expensify uses it at scale: https://bedrockdb.com/

      Delete
  14. Here are a few more thoughts on reliability of Go apps:

    http://bec-systems.com/site/1625/why-are-go-applications-so-reliable

    ReplyDelete
  15. And the binary with
    https://github.com/txthinking/joker
    https://github.com/brook-community/boa

    ReplyDelete

Why We Didn't Use AWS Lambda: EC2 Fits Better (For Our Golang App)

This is going to generate some hate.  But technology is complicated.  You *will* be able slice the numbers differently with different assump...