[{"content":"There\u0026rsquo;s a formula I keep coming back to:\nSuccess = Quality × Quantity × Continuity\nI like it because it\u0026rsquo;s not a metaphor, it\u0026rsquo;s an actual multiplication. And multiplication has a brutal property: if any factor is zero, the result is zero. It doesn\u0026rsquo;t matter how high the other values are.\nThis has a direct consequence for how teams (and individuals) should think about their work.\nEvidence: the pottery class # Before diving into the argument, it\u0026rsquo;s worth noting that the relationship between quantity and quality has been observed well outside software. There\u0026rsquo;s a well-known parable (popularized in the book Art and Fear by Bayles and Orland) about a ceramics teacher who divides a class into two groups: one graded purely on the number of pots produced, the other on the quality of a single perfect pot. At the end of the semester, all the best pots came from the quantity group. By making hundreds of pots, they learned what the quality group only theorized about.\nThe original story was actually about a photography class taught by Jerry Uelsmann at the University of Florida; the authors changed it to ceramics because they\u0026rsquo;d referenced photography too much elsewhere in the book. Regardless of the medium, the observation holds: iteration produces learning, and learning produces quality. You can\u0026rsquo;t think your way to expertise that requires doing.\nThis maps directly onto the formula. The quality group had high aspirations (Quality ≈ 1) but Quantity ≈ 0. The product of their semester was accordingly modest.\nThe discussion that produces nothing # You\u0026rsquo;ve seen this. A problem is identified. A meeting is scheduled. People share strong opinions about the right approach. An hour later, everyone leaves with a richer appreciation of the problem space and zero new information about whether any solution actually works.\nDiscussions feel productive. They\u0026rsquo;re effortful, they involve smart people, and they create the sensation of progress. But a discussion, however good, has Quantity = 0. No output, no feedback, no data. Multiply that by any Quality score you want: the result doesn\u0026rsquo;t change.\nPatrick Dubroy makes a related point in his recent post Fast is better than slow: the reason fast programmers are better isn\u0026rsquo;t raw speed, it\u0026rsquo;s that they get data sooner. Every cycle of build-and-observe teaches you something a discussion cannot. Waiting, whether out of perfectionism or habit, delays that learning without making it cheaper.\nThe smoothness argument # Marcus Raitner\u0026rsquo;s Slow is smooth, smooth is fast comes at this from a different angle, and at first it seems to contradict Dubroy. Raitner argues for sustainable pace over frantic output: Amundsen\u0026rsquo;s steady 15 miles a day beating Scott\u0026rsquo;s irregular bursts. The point isn\u0026rsquo;t to slow down, it\u0026rsquo;s to avoid the false speed of haste that generates errors, rework, and eventually, collapse.\nThese two positions aren\u0026rsquo;t in conflict. They operate at different levels:\nDubroy is talking about iteration speed: don\u0026rsquo;t delay starting, don\u0026rsquo;t polish before shipping, get the PR out. Raitner is talking about system pace: don\u0026rsquo;t burn out, don\u0026rsquo;t confuse activity with progress, keep a rhythm you can sustain indefinitely. In terms of the formula: Dubroy addresses Quantity, Raitner addresses Continuity. Both matter. You can\u0026rsquo;t multiply them away.\nQuality is not what you think it is # The trap most teams fall into is treating Quality as the primary variable and trying to maximize it before touching the others. Ship only when it\u0026rsquo;s ready. Discuss until the approach is sound. Wait until you\u0026rsquo;re confident.\nBut Quality in isolation is not success, it\u0026rsquo;s inventory. Code that isn\u0026rsquo;t running, features that aren\u0026rsquo;t used, decisions that aren\u0026rsquo;t tested against reality. And there\u0026rsquo;s a subtler problem: your Quality estimate before shipping is based on incomplete information. The feedback you get after shipping is the data that tells you whether your quality assessment was even correct.\nOliver Burkeman\u0026rsquo;s 70% rule, cited by Dubroy, addresses this directly: if you\u0026rsquo;re 70% satisfied, ship it. Not because quality doesn\u0026rsquo;t matter, but because the gap between 70% and 100% is largely speculation about what \u0026ldquo;done\u0026rdquo; means, and that speculation is cheaper to resolve with data than with further discussion.\nThe AI trap: when cheap iterations become noise # There\u0026rsquo;s a new version of this problem worth naming. AI tools (code assistants, generative prototyping, vibe coding) have collapsed the cost of iteration dramatically. A feature that used to take days can be roughed out in hours. This should, in principle, be a pure win for the formula: Quantity goes up, learning accelerates, Quality follows.\nBut cheap iterations introduce their own failure mode: the \u0026ldquo;just one more\u0026rdquo; trap. When generating a variation costs almost nothing, the temptation is to keep going, refining, tweaking, trying a slightly different approach, without ever committing to an output or exposing it to real feedback. AI can loop indefinitely without producing any real-world signal.\nThe Quantity in these cases is high. But if none of the iterations are ever shipped or tested against reality, Continuity of learning is broken. You\u0026rsquo;re running in place, not moving forward. This is a new instance of what Melissa Perri calls the Build Trap in her book Escaping the Build Trap: organizations that measure success by outputs rather than outcomes, shipping features to feel productive rather than to solve actual problems. Worth noting: Perri made that argument in 2018, well before the current rise of LLMs and agentic AI. The trap is not new, and it was never really about tooling. But AI lowers the cost of output so dramatically that an old trap becomes even easier to fall into.\nThis is precisely where Amundsen\u0026rsquo;s discipline becomes more relevant, not less. He didn\u0026rsquo;t stop at 14 miles because conditions were bad. He stopped at 15 because that was the plan, and plans exist to prevent both under- and over-exertion. In an AI-assisted workflow, the equivalent is deciding in advance: what question are we trying to answer with this iteration? When do we stop and look at results? What does \u0026ldquo;done enough to test\u0026rdquo; mean?\nCheap iterations are a gift. But they need a frame around them, or they become a different kind of procrastination, one that feels productive because code is being generated, but produces no more feedback than the pottery group that never touched clay.\nWhat this means in practice # The formula suggests a specific priority ordering when you can\u0026rsquo;t optimize all three factors simultaneously:\nProtect Continuity first. Burning out, creating unsustainable processes, or building a culture of heroics sets Continuity toward zero and eventually zeroes out everything else. This is Raitner\u0026rsquo;s point.\nMaximize Quantity second. More iterations, more feedback cycles, more small bets. This is where learning happens. A team that ships ten small things learns more than a team that ships one large thing, even if the large thing has higher unit quality.\nLet Quality rise with feedback. Quality improves fastest when you have real data. Trying to achieve high quality before feedback is guesswork. The loop is: ship at 70%, observe, adjust, repeat. Each cycle moves the quality floor up.\nThe compounding effect # This is where the multiplication really pays off. If you run the formula over time (with Continuity kept high, Quantity increasing, and Quality improving with each cycle) the product compounds. Not because any single output is exceptional, but because the feedback loop itself becomes the engine.\nThat\u0026rsquo;s not a new idea. It\u0026rsquo;s what agile retrospectives are for, what continuous delivery is for, what lean startup is for. But it\u0026rsquo;s easy to lose sight of it when a single hard problem is in front of you and a good discussion feels more productive than a rough prototype.\nIt usually isn\u0026rsquo;t.\nRelated reading:\nSlow is smooth, smooth is fast, by Marcus Raitner Fast is better than slow, by Patrick Dubroy Speed matters, by Jamie Brandon (linked from Dubroy\u0026rsquo;s post) Escaping the Build Trap, by Melissa Perri Art and Fear, by David Bayles and Ted Orland ","date":"10. Juni 2026","permalink":"https://www.gesellix.net/posts/success-in-iterations/","section":"Posts","summary":"","title":"The Multiplication Trap: Why Feedback Beats Perfection"},{"content":"On May 6, 2026, Bose shuts down SoundTouch cloud services. But before you assume this turns your speaker into a paperweight: Bose is also pushing an automatic app update that preserves the basics — device setup, remote control, multiroom groups, and streaming via AirPlay, Bluetooth, Spotify Connect, or AUX.\nSo the speakers keep working. Just not completely.\nWhat you lose with the cloud: presets. Those numbered buttons on the device and in the app — gone. In-app music browsing — no more browsing TuneIn or other services from within the SoundTouch app. SoundTouch 10 stereo pairing — also a casualty. And no more security updates, which is its own quiet concern.\nFor a lot of people, losing presets is losing the primary way they use these speakers. One tap to start the morning radio station. That\u0026rsquo;s the workflow, and it stops working tomorrow.\nUnless you prepared. Here\u0026rsquo;s what I built, and why.\nThe SoundTouch Community Had Already Started # When Bose announced the cloud shutdown, my first thought was: \u0026ldquo;Someone has probably already started working on a local replacement.\u0026rdquo; That instinct was right.\nThe first project I found was SoundCork by Deborah Kaplan and contributors. It\u0026rsquo;s a Python FastAPI service that intercepts the Bose cloud API calls — specifically the BMX (Bose Media eXchange) and Marge endpoints that the speakers use for account management, preset sync, and media browsing — and answers them locally.\nSoundCork pioneered the entire approach. It figured out which endpoints matter, what the request/response shapes look like, and how to redirect a speaker away from Bose\u0026rsquo;s infrastructure and toward a local service. Without that work, I\u0026rsquo;d have spent months on reverse engineering alone. Instead, I could build on a solid foundation.\nCredit where it\u0026rsquo;s due: SoundCork is the blueprint. My project wouldn\u0026rsquo;t exist without it.\nWhy Go? # I wanted something I could ship as a single binary — download and run, no runtime to install. Cross-platform without a package manager. Goroutines instead of async. And honestly: Go is the language I reach for when I want something I\u0026rsquo;ll still understand in two years.\nThere\u0026rsquo;s one more reason that only became clear later: Go cross-compiles to armv7 without any fuss, and the SoundTouch speakers themselves run on armv7 hardware. Someone already verified that the AfterTouch binary executes on a SoundTouch 20 without modifications (issue #196). That opens up an interesting future where AfterTouch runs directly on the speaker, with no separate server required.\nThe result is github.com/gesellix/bose-soundtouch — a Go toolkit for SoundTouch devices that includes a full local cloud replacement called soundtouch-service. Internally I\u0026rsquo;ve been calling it AfterTouch.\nWhat AfterTouch Does # At its core, AfterTouch is a local HTTP server that the speakers talk to instead of Bose\u0026rsquo;s cloud. Once a speaker is redirected — more on that in a moment — it uses AfterTouch for everything that used to require an internet connection:\nPresets: Store and retrieve up to six presets per speaker, synced across devices on the same account. The numbered buttons work again. TuneIn and media browsing: The BMX service lets the speaker browse and play internet radio stations the same way it always did. Account and device management: The Marge service handles device registration, source learning, and recent items tracking. Spotify Connect: Full ZeroConf priming with Diffie-Hellman key exchange so Spotify authentication works without relying on Bose\u0026rsquo;s relay. Multiroom zones: Group speakers together and control them as a unit. There\u0026rsquo;s a web UI at http://localhost:8000 that handles the whole setup. No config files to write.\nRedirecting the Speaker # The tricky part is getting the speaker to talk to AfterTouch instead of *.bose.com. There are two approaches, and the web UI guides you through both.\nXML redirect uploads a configuration file directly to the speaker via its local Web API. It\u0026rsquo;s the minimal approach — it covers only the specific endpoints registered in that config, without touching your network infrastructure.\nDNS redirect runs a custom DNS server on your network so that every outgoing request from the speaker to a Bose hostname resolves to the AfterTouch server instead. This catches all traffic at once. It requires HTTPS — AfterTouch manages its own certificate authority, and the web UI walks you through trusting the CA cert on each speaker.\nBeyond What the Original App Could Do # Once you have local control, you\u0026rsquo;re not limited to what Bose shipped. This is where a community-maintained solution can actually improve on the original.\nCLI and home automation: soundtouch-cli controls any speaker over the local network — play, pause, volume, presets, multiroom zones, device discovery. All scriptable. Pipe it into a cron job, trigger it from a webhook, integrate it with whatever home automation stack you run. The original app never exposed this.\nReal-time event streaming: The speakers speak WebSocket for status updates. AfterTouch exposes those events, so you can build integrations that react to speaker state rather than polling. Track changes, volume adjustments, playback status — all available programmatically via WebSocket. The official app never gave you this.\nBackup before shutdown: soundtouch-backup saves your Bose cloud account — presets, paired devices, music sources — while the cloud is still running. That data is what AfterTouch uses to restore your full setup locally.\nTraffic transparency: An optional proxy mode logs everything the speaker sends and receives. It\u0026rsquo;s how much of the protocol was documented in the first place — and it\u0026rsquo;s available to anyone who wants to understand or extend what their speaker is doing. LOG_PROXY_BODY=true soundtouch-service gives you visibility Bose never provided.\nGo library: pkg/client is an importable package. If you want to build your own integration — a custom dashboard, a voice assistant hook, a Home Assistant component — the library gives you a typed API over the SoundTouch Web API with WebSocket event support included.\nThe Migration Path # The recommended sequence:\nRun soundtouch-backup all to snapshot your account and device state from the Bose cloud. Start soundtouch-service on a machine with a stable local address, e.g. http://localhost:8000. Open that address in your browser and follow the migration steps. Choose your redirect method — XML for minimal coverage, DNS to catch all speaker traffic. Install the CA certificate on each speaker (the web UI shows how). Verify presets load and TuneIn browsing works. After that, the speaker doesn\u0026rsquo;t know Bose\u0026rsquo;s cloud is gone.\nWhat\u0026rsquo;s Still Missing # I want to be honest about the gaps. SoundCork has a more flexible BMX service registry — a JSON file you can edit to add or mock new streaming providers without touching code. AfterTouch hardcodes the service list, which covers the common cases but is less extensible. That\u0026rsquo;s on the roadmap.\nStereo pair management for SoundTouch 10 is partially implemented. Basic group CRUD works; the finer-grained master/slave pairing logic is still incomplete.\nThe most intriguing open question is whether AfterTouch can run on the speaker hardware itself. The speakers use an armv7 processor, and the binary already executes on a SoundTouch 20 — the remaining challenge is getting it to start at boot and expose the right ports. If that works out, the whole setup becomes self-contained: no separate server, one speaker hosts the service for the entire network.\nAnd some things AfterTouch can\u0026rsquo;t solve: no security updates is a real concern that no community project addresses. The speakers are running firmware that Bose will no longer patch. Running them on a firewalled private network (as Bose themselves recommends) is the pragmatic response, but it\u0026rsquo;s worth being clear that local emulation doesn\u0026rsquo;t fix the security picture.\nThe Bigger Picture # Hardware that stops working because a vendor shuts down a cloud service is a recurring frustration. The SoundTouch community — SoundCork, ÜberBöse, OpenCloudTouch, SoundTouch Plus — built a body of knowledge through reverse engineering and documentation that makes projects like this possible. That\u0026rsquo;s the part that genuinely impresses me: a handful of people, none of them at Bose, figured out enough of the protocol to keep the devices running. Bose did release the speaker\u0026rsquo;s local Web API documentation when they announced the shutdown, which helped — though the cloud-side API that AfterTouch had to reverse-engineer was a different matter entirely.\nAfterTouch is my contribution to that ecosystem. It stands on SoundCork\u0026rsquo;s shoulders. I hope it keeps a few more speakers out of the bin.\nThe project is at github.com/gesellix/bose-soundtouch. The Survival Guide has the full picture, and the Migration Guide walks through every step.\n","date":"5. Mai 2026","permalink":"https://www.gesellix.net/posts/aftertouch-bose-soundtouch/","section":"Posts","summary":"","title":"AfterTouch: Keeping Bose SoundTouch Speakers Fully Featured After the Cloud Shutdown"},{"content":"In June 2015, someone asked a seemingly simple question in the docker-java1 issue tracker: How can I send STDIN to a running container? That topic sparked nearly ten years of discussion, workarounds, and technical evolution. The problem wasn\u0026rsquo;t Docker itself, but a hidden mechanism in its Remote API that few HTTP clients could handle properly. This is the story of how persistence, open collaboration, and technical curiosity finally brought proper connection hijacking support to OkHttp—adding one more solid option to Java\u0026rsquo;s Docker ecosystem.\nHistory, Challenges, and Platform Struggles # The core issue lies in Docker\u0026rsquo;s use of Connection Hijacking2 for certain endpoints like attach and exec. After an initial HTTP request, the connection gets \u0026ldquo;hijacked\u0026rdquo; into a bidirectional raw stream where STDIN, STDOUT, and STDERR are multiplexed. While this follows RFC 7230 §6.7 legitimately, it\u0026rsquo;s rare enough that most HTTP clients never needed to handle it—except for WebSockets.\nThe Four Transport Evolution # docker-java1, the most popular Java library for Docker integration, experimented with multiple HTTP transports over the years, each with distinct strengths and limitations:\nJersey (≤2015): The original JAX-RS based transport was simple and reliable for basic Docker operations, but had no support for connection hijacking whatsoever.\nNetty (December 2015): The first breakthrough came with PR #397, which introduced Netty as a transport specifically to solve the hijacking problem. It worked beautifully on Linux using Netty\u0026rsquo;s native epoll transport, finally enabling attach and exec functionality. However, cross-platform support never materialized—macOS lacked Unix domain socket support in Netty at the time, and Windows named pipes were never implemented.\nOkHttp (March 2020): The community\u0026rsquo;s next hope was OkHttp3—lightweight, modern, and cross-platform. docker-java added OkHttp support in version 3.2.0, and shortly after introduced a HijackingInterceptor in PR #1347. The solution worked, but only by accessing OkHttp\u0026rsquo;s internal, unsupported APIs through Internal.instance. This fragility was a known compromise from day one. Notably, OkHttp maintainers participated in the docker-java discussions and subsequently opened issue #5874 to add official HTTP/1.1 Upgrade support—though implementation would take several more years.\nApache HttpClient 5 (June 2020): The final transport arrived with comprehensive feature support via PR #1358, but early versions (5.0-5.1) lacked the necessary APIs for proper Upgrade handling. Only with Apache HttpClient 5.2 GA in November 2022 did the library deliver robust \u0026ldquo;HTTP protocol upgrade (async)\u0026rdquo; support, making it the first truly stable, cross-platform solution for hijacking.\nThe Testcontainers Migration Story # Testcontainers-java4, another crucial project in the Java-Docker ecosystem, experienced this evolution firsthand. Initially using docker-java\u0026rsquo;s Jersey transport, they moved to OkHttp around 2018-2019 for its cross-platform promise and lighter footprint. The OkHttp-based solution served them well despite the internal API dependency.\nHowever, by 2021-2022, Testcontainers made a strategic decision to migrate from OkHttp to Apache HttpClient 5. Several factors drove this change:\nStability concerns: Relying on OkHttp\u0026rsquo;s internal APIs (Internal.instance) was inherently fragile and could break with any OkHttp update OkHttp\u0026rsquo;s Kotlin migration: As OkHttp increasingly adopted Kotlin internally, the Java interoperability story became more complex Feature completeness: Apache HttpClient 5 supported every Docker API use case without requiring workarounds or internal API access Official upgrade support: With HC5\u0026rsquo;s 5.2 release, connection hijacking became a first-class, documented feature This migration was completed in early 2022 with PR #5113, making Apache HttpClient 5 the default transport for Testcontainers.\nPlatform-Specific Limitations # Despite all these transport improvements, some fundamental platform challenges remain unresolved:\nLinux: Full hijacking support has been available since Netty\u0026rsquo;s introduction in December 2015. All transports work reliably with Unix domain sockets.\nmacOS: Support varies by transport and Docker configuration. TCP-based connections generally work, but Unix socket reliability depends on the specific transport implementation. Note that neither docker-java nor Testcontainers officially support alternative runtimes like Colima.\nWindows: The most challenging platform remains partially supported. Connection hijacking via Windows Named Pipes has never been properly implemented in any Java Docker client. On Windows, the TCP API is often considered as stable alternative to the native IPC mechanism.\nThis platform inconsistency has been a persistent source of frustration for developers trying to build truly cross-platform Docker tooling in Java. But hey, at least it kept us busy for a decade!\nPersonal Reflections on Open Source Collaboration # Working on this connection hijacking problem over the years taught me valuable lessons about open source development, particularly through my recent collaboration with Square\u0026rsquo;s OkHttp team. My work on gesellix/docker-client5—an alternative to docker-java—made me stick with the hijacking problem and continue searching for proper solutions rather than accepting workarounds. I have to admit: I\u0026rsquo;m a big fan of OkHttp due to its simplicity, small footprint, and few transitive dependencies, which is why my docker-client relies solely on OkHttp. Having my own Docker client implementation meant I needed reliable, long-term solutions, not fragile workarounds that could break with library updates.\nThe Power of Direct Contribution # For years, the Java Docker community found various solutions to handle OkHttp\u0026rsquo;s limitations. While docker-java and Testcontainers had working solutions with Apache HttpClient, we also built interceptors, used internal APIs, and accepted fragility as the price of functionality with OkHttp. The breakthrough came when I decided to contribute directly to OkHttp to provide a proper solution for this use case.\nIn 2024, I began implementing a proper, public connection hijacking API for OkHttp. The maintainers, including Jesse Wilson, were incredibly welcoming and collaborative. Rather than dismissing a niche use case, they embraced the technical challenge and helped refine the implementation. Importantly, OkHttp saw this as an opportunity to refactor their WebSocket implementation to rely on a more generic Socket abstraction—making the feature beneficial beyond just Docker\u0026rsquo;s use case. This is open source at its best—when curiosity meets initiative, supported by a culture of openness.\nPatience as a Technical Skill # This decade-long journey taught me that some problems require patience, not just technical skill. Libraries mature, maintainers change, and the ecosystem evolves at its own pace. The \u0026ldquo;right\u0026rdquo; solution often emerges when the timing aligns—when maintainers have bandwidth, when the technical foundation is ready, and when the community need is clear.\nRushing to implement workarounds (as we did with OkHttp\u0026rsquo;s internal APIs) provided short-term relief but created long-term technical debt. Sometimes the better path is to engage with upstream projects and accept that the timeline might be measured in years, not sprints.\nThe Culture of Collaboration # Square\u0026rsquo;s OkHttp team exemplifies what makes open source powerful. They could have easily dismissed connection hijacking as too niche or too complex. Instead, they saw it as an interesting technical challenge worth solving properly. This collaborative spirit—choosing engagement over dismissal—is what transforms individual libraries into ecosystem foundations.\nLooking Forward # By 2025, official Connection Hijacking support has finally landed in OkHttp, ending a decade of workarounds. The gesellix/docker-client5 now builds on a clean, OpenAPI-based foundation through docker-client/docker-remote-api6. Docker attach and interactive exec operations are finally stable across all supported platforms.\nThis isn\u0026rsquo;t just a technical success—it\u0026rsquo;s a reminder that open source works best when we engage with the challenges directly, contribute upstream rather than just consuming, and maintain the patience to see problems through to their proper resolution.\nThe long road to connection hijacking in Java taught me that the most satisfying solutions aren\u0026rsquo;t the quick fixes or clever workarounds, but the ones that make the entire ecosystem stronger. Sometimes that takes ten years. Sometimes that\u0026rsquo;s exactly what it should take.\nLooking ahead, one remaining challenge is adding proper Windows Named Pipe support for upgraded connections. While the TCP API provides a stable alternative on Windows, native Named Pipe support would complete the cross-platform story and provide true parity with Docker\u0026rsquo;s native IPC mechanisms.\nThank You # A special thank you to Square\u0026rsquo;s OkHttp team — Jesse Wilson, and the entire team — for their openness, technical expertise, and collaborative spirit. Their willingness to embrace a niche use case and turn it into a broader architectural improvement exemplifies the best of open source development.\nThis article was created with the assistance of Claude AI to help structure the content, organize the historical timeline, and refine the technical explanations.\ndocker-java/docker-java - The most popular Java Docker client\u0026#160;\u0026#x21a9;\u0026#xfe0e;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nDocker API: Container Attach - Official Docker API documentation\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nsquare/okhttp - HTTP client for Java and Android\u0026#160;\u0026#x21a9;\u0026#xfe0e;\ntestcontainers/testcontainers-java - Integration testing with Docker\u0026#160;\u0026#x21a9;\u0026#xfe0e;\ngesellix/docker-client - Alternative Java Docker client\u0026#160;\u0026#x21a9;\u0026#xfe0e;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\ndocker-client/docker-remote-api - OpenAPI-based Docker API client\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2. November 2025","permalink":"https://www.gesellix.net/posts/connection-hijacking-for-docker-in-okhttp/","section":"Posts","summary":"","title":"The Long Road to Connection Hijacking for Docker in OkHttp"},{"content":"For some years there are those old DVDs, spread in several boxes, waiting to be watched. I have some movies I just can\u0026rsquo;t get rid of, but I also don\u0026rsquo;t want to keep an unused DVD player standing in the tv cupboard. Let\u0026rsquo;s face it: I prefer choosing the movie by using my mobile phone and not by playing disc jockey.\nMeanwhile, I got my hands on a LincStation N1, put some storage inside, and made it work as a simple NAS. It\u0026rsquo;s easy now to digitize my DVDs and uploading them to a local network share. The LincStation can now run a MiniDLNA server to share my videos to any UPnP/DNLA compatible client. What I really want is to watch the videos on my tv, though. But in fact we\u0026rsquo;re talking about my Chromecast. Chromecast only requires a compatible client or app which tells it about the video server.\nOne of those clients is the BubbleUPnP for DLNA/Chromecast Android app, which can cast my videos to the Chromecast on my TV.\nThat\u0026rsquo;s all I need: a server with my video files, shared via DNLP, cast via app to the Chromecast.\nIt just works!\n","date":"23. Oktober 2024","permalink":"https://www.gesellix.net/posts/a-simple-local-media-server/","section":"Posts","summary":"","title":"A simple local media server "},{"content":"Docker started a revolution with its new approach to use containers as abstraction layer for applications. That was the perfect timing for our new products and our thoughts about building and deploying web services.\nOthers also started Java implementations for the RESTful Docker api, but our requirements didn\u0026rsquo;t quite match the existing projects and we also wanted to iterate fast enough to adopt to our new learnings and changing requirements. So we implemented our own variants of a Docker client and a Gradle plugin for Java. The integration with Gradle was a bit rough at the edges, but good enough to present our solution at the Gradle Summit (see gesellix.net/gradle-summit-2014-follow-up/ for more details). A little side note: I\u0026rsquo;m actually quite proud of myself of that presentation: my very first time in front of so many unknown people - in English - far away from home - best experience ever :)\nTen years later, the projects are still alive and healthy. Naturally, they became more mature and stable regarding the feature set. Due to our tendency to play with new tools or concepts, one might also say that the sub-projects und sub-libraries are a bit over-engineered, but hey, developing makes fun!\nThis article is a little reminder to myself how the projects have grown, how open source contributors have added even more value, and how the whole Docker community has been and still is a place of friendly and constructive exchange.\nSome technical details at the end. Here are the \u0026ldquo;birthday\u0026rdquo; commit logs of the Docker-Client and the Gradle-Docker-Plugin:\nFirst commit of the Docker-Client First commit of the Gradle Docker plugin ","date":"1. Mai 2024","permalink":"https://www.gesellix.net/posts/ten-years-docker-client-and-gradle-plugin/","section":"Posts","summary":"","title":"Ten Years of a Docker-Client for Java and a Docker-Plugin for Gradle"},{"content":"After 10 years without a bike, I finally got my hands on a gravel bike. I wasn\u0026rsquo;t aware how much I missed biking!\nHow I got back to biking # In my youth I often used my bike and even later during my time in Berlin, going by bike was a great feeling. Well, thinking of Berlin: that\u0026rsquo;s where my bike was stolen. Nothing dramatic, but I also didn\u0026rsquo;t replace it. Until now.\nA few years ago we moved from Berlin to Bonn, and I luckily continued to work remotely for the company in Berlin. Well, that\u0026rsquo;s the good part. The bad part: I looked at the same walls for 99% of the time, so after some years of going fully-remote I needed a change. I changed the employer with a focus on their office to be right around the corner.\nNow I am less than half an hour away from the office, so I can choose on a daily basis whether to stay at home (some days are heavily influenced by my kids), or go to the office and meet my colleagues.\nThis is very convenient and also opened the door to go by bike again. Many companies like my current employer provide benefits like Jobrad, which was a great chance for me to consider a more advanced and fun bike without the need to rob a bank :)\nChoosing a bike # I researched bike options and found one at Canyon, with their headquarters quite near - along with a showroom and the possibility to test several bike models. They also provide a good consultation without expecting you to instantly buy a bike! That helped a lot to decide between a more sensible (a pure commuting bike like the Canyon Commuter) and a more exciting decision (a bikepacking/gravel bike like the Canyon Grizl).\nAfter a night\u0026rsquo;s sleep I ordered the Grizl 7 AL, applied for leasing via Jobrad and sent my employer the form to approve the Jobrad as a new \u0026ldquo;Dienstwagen\u0026rdquo;. By the way: I ordered on Saturday and received the bike after only 6 days. I really didn\u0026rsquo;t expect the whole process to work so well! Canyon does a great job of preparing the bike, so that I required less than an hour to assemble it and go for the first test ride.\nDuring the next weekend I already checked the tour to the office: not riding a bike for the last 10 years, I wanted to ensure that the route to the office would work for me and my level of fitness. Let\u0026rsquo;s put it like this: there aren\u0026rsquo;t any steep tracks between my home and the office - and the route itself, crossing the Rhine, is quite enjoyable.\nThe bike is not enough # The season is probably not the best one to start biking, but the unsteady weather also helps to think about additional gear, especially the right clothes. Ten years ago, I might not have considered to even gather information about \u0026ldquo;how to ride a bike\u0026rdquo;, but in times of information overload via internet and social media, it was just too easy to learn about the options, requirements, tips, and tricks.\nThere are great videos at YouTube, for example the German channel by Anke is Awesome. Anke tells you down-to-earth about the relevant bits and answered more or less all of my questions. Leveraging YouTube\u0026rsquo;s recommendation algorithm (in a positive way), I watched several other videos and channels, sometimes about required gear for shorter versus longer trips, challenges like Brevet or Randonneuring, and I became also curious for bike backing.\nGoing (biking) all-in # Long story short: I guess I\u0026rsquo;m going all-in and I\u0026rsquo;ll keep inspirational stories by Giant Cheerio and Wiebke Lühmann in my mind. Yet, before going on a ride of 10.000km, I\u0026rsquo;ll consider a Brevet of 200km or a tour up and down the hills to the Westerwald. Other channels by GCN auf Deutsch and Bastian Brutzer will help with their tips and tricks, and I will start with shorter and easier rides to Cologne and Koblenz.\nI\u0026rsquo;ll keep you updated!\n","date":"22. Januar 2023","permalink":"https://www.gesellix.net/posts/on-the-bike-again/","section":"Posts","summary":"","title":"On the Bike Again"},{"content":"Keep it simple stupid KISS in software development isn\u0026rsquo;t always easy. After 10 years of maintaining and evolving a little search engine, it\u0026rsquo;s time for a review.\nOverview # The project started with a question from a former colleague whether we could build a search engine about planned topics in journals, magazines, and newspapers. Me as a developer has a an instant desire to answer such a question with a clear Yes: greenfield projects are great playgrounds, places to learn and hone one\u0026rsquo;s skills.\nThe tech stack started with a proof of concept using CouchDB, Elasticsearch, Spring Framework and Wicket. Wicket had been replaced by an Angular frontend after a while, plain Spring became Spring Boot, but the rest stayed the same until today.\nWe\u0026rsquo;re currently running the services on DigitalOcean, with the provisioning and deployment being modeled in Ansible. Docker replaced Debian/Ubuntu packages some years ago, and Terraform has been added to the mix since 2022.\nSimplicity Works # Pictures tell more than words, so here is an architectural overview of the whole setup:\nYes: you\u0026rsquo;ll notice many best practices missing. That\u0026rsquo;s partially for simplicity of the diagram, but mainly due to the simplicity of the actual architecture. We usually deploy services like Elasticsearch and CouchDB in a cluster, but in this case it\u0026rsquo;s really just as simple as on a local developer machine. The Frontend, Backend, and the reverse proxies should also be deployed as replicated instances for better scale and elasticity - but they\u0026rsquo;re not.\nWe certainly won\u0026rsquo;t stand against a DoS attack, but the application just works for the registered customers with a complete feature set, and anonymous users with their restricted feature set. A whitelabelled variant is included in a partner\u0026rsquo;s product, so there are additional users not directly subscribed to our appliation putting some more load on our application.\nIn fact, this is not about the technology being simple. It\u0026rsquo;s about a clear product with simple, yet effective features: similar to the input field on Google Search, a user only has to enter a query and optionally change some filter settings like date ranges or the kind of publication. Technology follows the product\u0026rsquo;s complexity - well, at least the developers or engineers shouldn\u0026rsquo;t artificially make the stack more complex than necessary.\nIn this case, focused on the user\u0026rsquo;s needs, all we need is a simple query, some pagination, maybe an export feature, and the option so save the query parameters for easy re-use. From the administrator\u0026rsquo;s point of view there\u0026rsquo;s an import feature to keep the data up-to-date, and a user management to maintain subscriptions. The developer, yours truly, is interested in short development cycles and the chance to understand the codebase even after some weeks of inactivity. With patterns like Infrastructure as Code (IaC) a developer can focus on the exciting parts and leave the boring processes to automation.\nThe fun part: along the years, several other tools grew due to individual requirements, and if you already followed my activities at github.com/gesellix, you won\u0026rsquo;t be surprised that those add-on projects are available as open source. As an example: we started with Debian packages to distribute the application artifacts, but migrated to Docker containers quite quickly. You can recognize that shift in the contributions to the Gradle Debian plugin compared to the development of the Gradle Docker plugin. I won\u0026rsquo;t go into the details about the older tools, which I might not even use myself anymore, but I still try to keep them up-to-date and usable for other people.\nWhat\u0026rsquo;s under the hood? # Back to the search engine. This year, we took a fresh start for the whole provisioning layer: due to some hosts being old enough for retirement and a major Elasticsearch upgrade in our backlog, we opted for true IaC by introducing Terraform and its DigitalOcean provider. A more spontaneous upgrade happened to the CouchDB from 1.6 to 3.2, which was a simple task given the straight forward CouchDB concepts\u0026hellip; did I mention that I love CouchDB?\nSo, we\u0026rsquo;re going to describe a fully working setup (from scratch!) with the only requirement of a database backup being imported to the fresh installation and the search index to be created from that fresh database.\nI\u0026rsquo;ll provide some code down below and if you\u0026rsquo;re going to try the setup for your own projects, than you can use DigitalOcean\u0026rsquo;s referral program. Simply follow this referral link to sign up, which helps yourself starting with a $100 credit, and myself earning a bit of money as well. Thanks!\nWith Terraform performing the initial deployment of hosts, domains, low-level stuff, and some basic cloud-init based configuration of the operating system, we need another step of configuring the operating system and adding required software packages. Ansible has served us well for the past years, so we still use it for provisioning and some deployment tasks. Provisioning is very simple, because we only need to install the Docker packages on top of some core packages. Everything else runs as a Docker container, being configured and run through Ansible modules.\nDocker allows us to choose between manually maintaining containers (docker run) and orchestrating tasks (docker service create) via Docker Swarm. Well, we don\u0026rsquo;t actually choose one of those modes, but use both: for our long-running processes like the storage layer, we use standard containers. Updating those instances usually requires downtime - and that\u0026rsquo;s fine for us, because we don\u0026rsquo;t update or re-deploy the databases very often. Things look different for the Frontend and Backend services: this is where features or bug fixes are added regularly, so we need a more convenient way of updating those services. We want it to happen in an automated fashion. Docker Swarm mode helps a lot and allows a zero-downtime deployment via declarative configuration. This is similar to Kubernetes - only less complex, with smaller resource requirements, and matching our needs.\nThe Frontend and Backend services are initially deployed via Ansible, but we don\u0026rsquo;t perform service updates that way: as long as we only want to update the parent image, we can use Docker\u0026rsquo;s service update api. The Docker api shouldn\u0026rsquo;t be exposed to the internet, so we added a tiny webhook service with access to the Docker engine. That webhook service accepts authenticated requests from a GitHub workflow. The GitHub workflow can be triggered on pull-request merge or even from a feature branch to trigger a preview deployment in a non-production environment.\nEverything we mentioned above isn\u0026rsquo;t exposed to the internet. To make the application usable for customers we added Traefik as reverse proxy. Traefik works great together with Docker (and Docker Swarm) and helps with the boring stuff like certificate management via Let\u0026rsquo;s Encrypt.\nIf you look close enough, you\u0026rsquo;ll notice two colored boxes in the picture above: we added two Docker networks to segregate the storage and the application layer, so we can reason about smaller parts of the whole stack and reduce the risk of failure in case of security issues or human error. The storage layer won\u0026rsquo;t ever be accessible from the internet or even the publicly available reverse proxy. The backend is the only service running in both networks.\nBefore we go into details about each part of the stack, let\u0026rsquo;s explain the use case for each storage service: the CouchDB is our primary database with the actual data being queried by the customers and also containing the user and configuration databases. We don\u0026rsquo;t use the CouchDB for the full-text queries, though: this is where Elasticsearch shines. During the import of updated data, we write everything into the CouchDB database and Elasticsearch. This is convenient in case of data loss in Elasticsearch, because everything is available as JSON in CouchDB, easily and fast to be synched. CouchDB is very easy operate (ok, it became a bit advanced since version 2 with its clustered setup, which is still configured as a single master node in our case) and can be backupped by copying the data volume. Replication to another instance works very well, too! The Redis database is for ephemeral user session metadata. Nothing exciting on that part.\nShow me the code! # After that overview about each part of the stack, we\u0026rsquo;re going to peek into the actual code. We\u0026rsquo;re going to describe the details in upcoming articles, along with actual code for you to play with or start your own projects.\nThe next article will cover Terraform and parts of the initial provisioning. So, stay tuned and in case you already have some questions, just get in contact via twitter.com/gesellix!\n","date":"4. September 2022","permalink":"https://www.gesellix.net/posts/start-small-keep-it-simple--overview/","section":"Posts","summary":"","title":"Start Small and Keep it Simple: Overview"},{"content":"This post will show you the steps it takes to run a custom LinuxKit based image on Linode.\nThis looks so simple as it is: Running #Docker #linuxkit on a @linode\nTobias Gesellchen (@gesellix), June 17, 2017\nAbout LinuxKit # But let\u0026rsquo;s start with the basics: LinuxKit is a tool to create immutable and minimal operating system images. As you might expect, it heavily leverages Docker images and uses containers to run services via containerd.\nDue to its simplicity you can configure your OS images with simple YAML files. Many examples are already available in the LinuxKit GitHub repository. I picked the sshd.yml as a start.\nThere are several main sections to configure the desired target image, e.g. the underlying Kernel, init processes, boot time executables, and the relevant services you\u0026rsquo;d like to run. Some sections like files allow to add custom files or specific configuration at build time.\nA LinuxKit config to run a sshd server looks like this, as taken from the LinuxKit repo:\n# examples/sshd.yml kernel: image: \u0026quot;linuxkit/kernel:4.9.x\u0026quot; cmdline: \u0026quot;console=ttyS0 page_poison=1\u0026quot; init: - linuxkit/init:17693d233dd009b2a3a8d23673cb85969e1dce80 - linuxkit/runc:3a4e6cbf15470f62501b019b55e1caac5ee7689f - linuxkit/containerd:04880f344709830aa4c938baa765764e644fc973 - linuxkit/ca-certificates:75cf419fb58770884c3464eb687ec8dfc704169d onboot: - name: sysctl image: \u0026quot;linuxkit/sysctl:3aa6bc663c2849ef239be7d941d3eaf3e6fcc018\u0026quot; services: - name: getty image: \u0026quot;linuxkit/getty:d0765e0a14733f9454010ac109a7c846a4e67fc5\u0026quot; env: - INSECURE=true - name: rngd image: \u0026quot;linuxkit/rngd:1fa4de44c961bb5075647181891a3e7e7ba51c31\u0026quot; - name: dhcpcd image: \u0026quot;linuxkit/dhcpcd:7d2b8aaaf20c24ad7d11a5ea2ea5b4a80dc966f1\u0026quot; - name: sshd image: \u0026quot;linuxkit/sshd:abc1f5e096982ebc3fb61c506aed3ac9c2ae4d55\u0026quot; files: - path: root/.ssh/authorized_keys source: ~/.ssh/id_rsa.pub mode: \u0026quot;0600\u0026quot; optional: true trust: org: - linuxkit I guess if you already know Docker, there\u0026rsquo;s nothing special to it. The interesting aspect is the usage of content hashes as tags, which you might be a bit unused with.\nAs the LinuxKit Readme already tells you how to get started, I\u0026rsquo;ll only post the bare commands I used for your convenience:\ngo get -u github.com/moby/tool/cmd/moby go get -u github.com/linuxkit/linuxkit/src/cmd/linuxkit git clone https://github.com/linuxkit/linuxkit cd linuxkit moby build -output iso-bios examples/sshd.yml I assume you have Golang already installed and Docker is running.\nThe exciting part is probably the last command where the moby tool is used to create a standard ISO image based on the sshd.yml config. When you start the command for the first time you\u0026rsquo;ll see moby downloading and processing the referenced Docker images, adding your SSH public key, and creating the desired output image as ISO format:\n$ moby build -output iso-bios examples/sshd.yml Extract kernel image: linuxkit/kernel:4.9.x Pull image: docker.io/linuxkit/kernel:4.9.x@sha256:1631c1ceea2007b308ae4efeee9179c1e7d578a5dedab54fbcc64746532f2828 Add init containers: Process init image: linuxkit/init:17693d233dd009b2a3a8d23673cb85969e1dce80 Pull image: docker.io/linuxkit/init:17693d233dd009b2a3a8d23673cb85969e1dce80@sha256:cb2c1a01777004f34e4e1400a1deab0c7c68e252f97d5387a6708e59483228ef Process init image: linuxkit/runc:3a4e6cbf15470f62501b019b55e1caac5ee7689f Pull image: docker.io/linuxkit/runc:3a4e6cbf15470f62501b019b55e1caac5ee7689f@sha256:e8603bafe28d72887a2db01886d326a6e309e6ad3b2ad88bb7cee9be260d2fc7 Process init image: linuxkit/containerd:04880f344709830aa4c938baa765764e644fc973 Pull image: docker.io/linuxkit/containerd:04880f344709830aa4c938baa765764e644fc973@sha256:dcf0518979f3611003d0459458f9a260d9f95cfe73afce034a3aef447a8e2b71 Process init image: linuxkit/ca-certificates:75cf419fb58770884c3464eb687ec8dfc704169d Pull image: docker.io/linuxkit/ca-certificates:75cf419fb58770884c3464eb687ec8dfc704169d@sha256:4bd4053003322a35f1effce6aa42066b22b5d9ea7dc638ef2f804df3b3b644f2 Add onboot containers: Create OCI config for linuxkit/sysctl:3aa6bc663c2849ef239be7d941d3eaf3e6fcc018 Pull image: docker.io/linuxkit/sysctl:3aa6bc663c2849ef239be7d941d3eaf3e6fcc018@sha256:189df608e87f237463858c86788da44b29e0bd8bd1a97fff7fcf7d32c1250edb Add service containers: Create OCI config for linuxkit/getty:d0765e0a14733f9454010ac109a7c846a4e67fc5 Pull image: docker.io/linuxkit/getty:d0765e0a14733f9454010ac109a7c846a4e67fc5@sha256:20bf26fa27071e02c83bc6d7ffa508687986051db04199b5ef9233e0395afe07 Create OCI config for linuxkit/rngd:1fa4de44c961bb5075647181891a3e7e7ba51c31 Pull image: docker.io/linuxkit/rngd:1fa4de44c961bb5075647181891a3e7e7ba51c31@sha256:711f86b810c78f9be4d8b343053c14cfc5aa5a4ae939b398e3f3d95bbb900ce9 Create OCI config for linuxkit/dhcpcd:7d2b8aaaf20c24ad7d11a5ea2ea5b4a80dc966f1 Pull image: docker.io/linuxkit/dhcpcd:7d2b8aaaf20c24ad7d11a5ea2ea5b4a80dc966f1@sha256:b1bef35accd165a4235ad301f9bef6544922cb6eafdb60268b05d8c9d0b1566b Create OCI config for linuxkit/sshd:abc1f5e096982ebc3fb61c506aed3ac9c2ae4d55 Pull image: docker.io/linuxkit/sshd:abc1f5e096982ebc3fb61c506aed3ac9c2ae4d55@sha256:727da1ef4a3f61050ed8164244610388f85578aea295b54a02d7b87c72b814a9 Add files: root/.ssh/authorized_keys Create outputs: sshd.iso You could also choose to create other output formats like the default kernel+initrd or a raw format for AWS. When using the default output format you can even test your created image on your local machine by simply booting it. This is how it looks like when running on Docker for Mac:\n$ moby build -output kernel+initrd examples/sshd.yml ... Create outputs: sshd-kernel sshd-initrd.img sshd-cmdline $ linuxkit run sshd virtio-net-vpnkit: initialising, opts=\u0026quot;path=/Users/gesellix/Library/Containers/com.docker.docker/Data/s50\u0026quot; virtio-net-vpnkit: magic=VMN3T version=1 commit=0123456789012345678901234567890123456789 Connection established with MAC=02:50:00:00:00:03 and MTU 1500 vsock init 2:0 = sshd-state, guest_cid = 00000003 early console in extract_kernel input_data: 0x0000000001ee53b4 input_len: 0x000000000070f533 output: 0x0000000001000000 output_len: 0x00000000015e23e4 kernel_total_size: 0x00000000011c6000 booted via startup_32() Physical KASLR using RDRAND RDTSC... Virtual KASLR using RDRAND RDTSC... Decompressing Linux... Parsing ELF... Performing relocations... done. Booting the kernel. [ 0.000000] Linux version 4.9.32-linuxkit (root@9ac68176ac52) (gcc version 6.3.0 (Alpine 6.3.0) ) #1 SMP Thu Jun 15 20:49:23 UTC 2017 ... [ 1.874566] Freeing unused kernel memory: 976K (ffffa135b2d0c000 - ffffa135b2e00000) Starting containerd Welcome to LinuxKit ## . ## ## ## == ## ## ## ## ## === /\u0026quot;\u0026quot;\u0026quot;\u0026quot;\u0026quot;\u0026quot;\u0026quot;\u0026quot;\u0026quot;\u0026quot;\u0026quot;\u0026quot;\u0026quot;\u0026quot;\u0026quot;\u0026quot;\u0026quot;\\___/ === ~~~ {~~ ~~~~ ~~~ ~~~~ ~~~ ~ / ===- ~~~ \\______ o __/ \\ \\ __/ \\____\\_______/ INFO[0000] starting containerd boot... module=containerd INFO[0000] starting debug API... debug=\u0026quot;/run/containerd/debug.sock\u0026quot; module=containerd INFO[0000] loading plugin \u0026quot;io.containerd.content.v1.content\u0026quot;... module=containerd type=io.containerd.content.v1 INFO[0000] loading plugin \u0026quot;io.containerd.snapshotter.v1.btrfs\u0026quot;... module=containerd type=io.containerd.snapshotter.v1 INFO[0000] loading plugin \u0026quot;io.containerd.snapshotter.v1.overlayfs\u0026quot;... module=containerd type=io.containerd.snapshotter.v1 INFO[0000] loading plugin \u0026quot;io.containerd.differ.v1.base-diff\u0026quot;... module=containerd type=io.containerd.differ.v1 INFO[0000] loading plugin \u0026quot;io.containerd.metadata.v1.bolt\u0026quot;... module=containerd type=io.containerd.metadata.v1 INFO[0000] loading plugin \u0026quot;io.containerd.grpc.v1.containers\u0026quot;... module=containerd type=io.containerd.grpc.v1 INFO[0000] loading plugin \u0026quot;io.containerd.grpc.v1.content\u0026quot;... module=containerd type=io.containerd.grpc.v1 INFO[0000] loading plugin \u0026quot;io.containerd.grpc.v1.diff\u0026quot;... module=containerd type=io.containerd.grpc.v1 INFO[0000] loading plugin \u0026quot;io.containerd.monitor.v1.cgroups\u0026quot;... module=containerd type=io.containerd.monitor.v1 INFO[0000] loading plugin \u0026quot;io.containerd.runtime.v1.linux\u0026quot;... module=containerd type=io.containerd.runtime.v1 INFO[0000] loading plugin \u0026quot;io.containerd.grpc.v1.tasks\u0026quot;... module=containerd type=io.containerd.grpc.v1 INFO[0000] loading plugin \u0026quot;io.containerd.grpc.v1.healthcheck\u0026quot;... module=containerd type=io.containerd.grpc.v1 INFO[0000] loading plugin \u0026quot;io.containerd.grpc.v1.images\u0026quot;... module=containerd type=io.containerd.grpc.v1 INFO[0000] loading plugin \u0026quot;io.containerd.grpc.v1.namespaces\u0026quot;... module=containerd type=io.containerd.grpc.v1 INFO[0000] loading plugin \u0026quot;io.containerd.grpc.v1.snapshots\u0026quot;... module=containerd type=io.containerd.grpc.v1 INFO[0000] loading plugin \u0026quot;io.containerd.grpc.v1.version\u0026quot;... module=containerd type=io.containerd.grpc.v1 INFO[0000] starting GRPC API server... module=containerd INFO[0000] containerd successfully booted in 0.019407s module=containerd [ 2.288861] tsc: Refined TSC clocksource calibration: 3097.775 MHz [ 2.290004] clocksource: tsc: mask: 0xffffffffffffffff max_cycles: 0x2ca711e180f, max_idle_ns: 440795221899 ns - 000-sysctl Starting service: \u0026quot;dhcpcd\u0026quot; INFO[0000] new shim started module=\u0026quot;containerd/execution\u0026quot; socket=\u0026quot;/var/lib/containerd/io.containerd.runtime.v1.linux/default/dhcpcd/shim.sock\u0026quot; Starting service: \u0026quot;getty\u0026quot; INFO[0000] new shim started module=\u0026quot;containerd/execution\u0026quot; socket=\u0026quot;/var/lib/containerd/io.containerd.runtime.v1.linux/default/getty/shim.sock\u0026quot; / # Starting service: \u0026quot;rngd\u0026quot; INFO[0000] new shim started module=\u0026quot;containerd/execution\u0026quot; socket=\u0026quot;/var/lib/containerd/io.containerd.runtime.v1.linux/default/rngd/shim.sock\u0026quot; [ 2.533703] IPVS: Creating netns size=2104 id=1 [ 2.533972] IPVS: ftp: loaded support on port[0] = 21 Starting service: \u0026quot;sshd\u0026quot; INFO[0000] new shim started module=\u0026quot;containerd/execution\u0026quot; socket=\u0026quot;/var/lib/containerd/io.containerd.runtime.v1.linux/default/sshd/shim.sock\u0026quot; [ 3.297574] clocksource: Switched to clocksource tsc / # uname -a Linux linuxkit-025000000004 4.9.32-linuxkit #1 SMP Thu Jun 15 20:49:23 UTC 2017 x86_64 Linux / # / # halt / # If you watched Inception and didn\u0026rsquo;t have any issues with the different levels of dreaming then the current example with a setup of sshd in a minimal LinuxKit in a hypervised Alpine Linux running on a Mac is certainly no problem to you\u0026hellip;\nLinuxKit on Linode # Ok, let\u0026rsquo;s run the sshd LinuxKit image in a more realistic scenario, namely on a provider like Linode. I chose Linode because I use it for my personal experiments, but the sshd.iso image should run on any KVM based provider. If you\u0026rsquo;re more into Amazon AWS, you can read a more specific introduction at the bee42 blog.\nThe folks at Linode already have a good article to get you started with custom images. So, all I needed to do was following their instructions - and even skipping some of the described steps, because the LinuxKit image doesn\u0026rsquo;t need a custom installer like other Linux distributions. So, here\u0026rsquo;s the essence of what you need to run LinuxKit on a Linode.\nSince we would now like to copy our .iso image to a Linode disc, we somehow have to provide the image online. I simply uploaded the sshd.iso to another server and temporarily made it available via Nginx:\nlocal$ scp sshd.iso gesellix@web.example.com:~/nginx-content/sshd.iso local$ ssh gesellix@web.example.com web$ docker run -d -p 8080:80 -v `pwd`/nginx-content:/usr/share/nginx/html:ro nginx:alpine Now the sshd.iso image is available on the internet and can be downloaded to any server. Let\u0026rsquo;s prepare one.\nOn Linode, you\u0026rsquo;ll need to create a new disc. The size depends on your needs, obviously, but I chose 1GB as a start. Then, create a new configuration profile, where all Filesystem / Boot Helpers are disabled and your newly created disc is mounted to /dev/sda.\nThen you\u0026rsquo;ll need to use the rescue mode with your disc being mounted at /dev/sda. You can use the Lish console to connect yourself to your Linode. The following commands will download your LinuxKit image and copy its contents to your empty disc:\nlinode/lish$ wget http://web.example.com:8080/sshd.iso linode/lish$ dd if=sshd.iso of=/dev/sda Almost done - you can now close the Lish console and reboot your Linode. After a short while you should be able to connect to your fresh instance via ssh:\nlocal$ ssh root@linode.example.com Welcome to LinuxKit linuxkit-f23c91e59867:~# Linode allows additional optimizations to increase compatibility with the Linode manager, but that\u0026rsquo;s probably a bit too specific for now.\nThe more interesting part is automation of all the steps above. Linode allows you to use their api and even provide a cli tool. As far as I could see they won\u0026rsquo;t allow full automation, yet, but I\u0026rsquo;ll give it a try at a later time.\nPlease note that LinuxKit is under heavy development. Usually, the architecture won\u0026rsquo;t change too much, but some command line options or the image description format might change.\nFeedback # If you have questions or any suggestions, please don\u0026rsquo;t hesitate to contact me via Twitter @gesellix! Thanks!\n","date":"18. Juni 2017","permalink":"https://www.gesellix.net/posts/running-docker-linuxkit-on-linode/","section":"Posts","summary":"","title":"Running Docker LinuxKit on Linode"},{"content":"Playing around with Docker Swarm mode really makes fun: you only need a single command to initialize a cluster on your local machine. The following line configures your Docker Engine as Docker Swarm manager:\ndocker swarm init The command output already tells you how to add additional worker nodes and looks similar to this:\ndocker swarm join \\ --token SWMTKN-1-somecharacters-andmorecharacters \\ 192.168.47.11:2377 You don\u0026rsquo;t necessarily have to add additional worker nodes, because the manager node can also be used as worker. If you wonder how to add more manager nodes, the Engine will help you if you enter this one:\ndocker swarm join-token manager In your production environment you might be tempted to have a more resilient setup, using 3 or 5 Swarm manager nodes (to build a quorum) and even more worker nodes depending on your workload.\nDeploying a replicated service then is a no-brainer:\ndocker service create --name nginx --publish 8080:80 --replicas 2 nginx:alpine Docker Swarm will manage your services with their tasks to be deployed on your cluster. Automated restarts, rolling updates, networking, internal DNS, simple load balancing, draining nodes, and built-in security are some of the features you get for free and without much hassle.\nBut why all the fuzz about that?\nLet\u0026rsquo;s first take a look at the status quo in my team and find out why we cannot simply switch to an orchestrator like Docker Swarm. If you want to skip the gossip and continue with the real stuff, just head over to the relevant section below.\nThe Good # We already posted an article series about the way we implemented a continuous deployment pipeline. The underlying concept hasn\u0026rsquo;t changed too much, so even the code samples on GitHub are still a good reference if you want to start with a small application with a small number of services.\nMeanwhile, our code base has grown - which is certainly a good thing. We still rely on Ansible to perform provisioning and blue-green deployments. The number of services has grown, though. More advanced stuff has been added, e.g. Consul-Template in combination with Registrator, and NGINX as reverse proxy. Our setup isn\u0026rsquo;t very special, there are plenty of articles out there for such a setup and you can find very concise examples to get you started.\nOur current setup makes it quite easy to add yet another microservice into our infrastructure. Since we wrote every Ansible role and playbook ourselves, we know where to tweak our settings or where to fix issues. Yet, such a setup with Consul/Registrator/NGINX isn\u0026rsquo;t trivial to maintain and using Ansible for deployments still feels a bit strange.\nThe Bad? # I personally have the impression that Ansible is very good at provisioning your nodes. But when it comes down to performing docker run, you\u0026rsquo;ll feel that the Docker module in an Ansible role doesn\u0026rsquo;t really comply with Ansible\u0026rsquo;s claim to be declarative. It works, it has become stable and can handle different Docker Engine versions, but it doesn\u0026rsquo;t feel right.\nWhen I look at an actual deployment, where our TeamCity goal runs an Ansible image to internally use docker-py, talking to a Docker Engine on another node and ultimately triggers the docker pull and docker run api commands, I wish for less abstractions.\nConsider the alternative: TeamCity could perform a simple docker -H target.swarm.local:2376 service update ....\nNothing more. Yeah, that\u0026rsquo;s kind of an understatement, but you get the idea.\nAnsible helped us to drop shell scripts when we started to scale our deployment pipeline with the growing number of services and nodes. In fact, we can consider TeamCity + Ansible + Consul + Registrator + Consul Template to be \u0026ldquo;our orchestration tool\u0026rdquo;. Over time, alternatives have shown up, and if I look at the shiny features of tools like Swarm, Kubernetes, and DC/OS, I ask myself why I should maintain Ansible roles along with the non-trivial setup of our home grown orchestration tool.\nMaybe you already know that article about the Configuration Complexity Clock. If not, please go and read it! I\u0026rsquo;ll wait for you to come back here.\n\u0026hellip;\nI wouldn\u0026rsquo;t really say that we should go full circle around that complexity clock. I would prefer to find better tools and stay at, don\u0026rsquo;t know, maybe 4 o\u0026rsquo;clock? Maybe what we need is a little bit of every hour on that clock? Only the good parts of each hour?\nMaybe yes. I want the hard coded actual bit to perform a container deployment - but I don\u0026rsquo;t want to maintain it myself. So I\u0026rsquo;ll need to configure some externally maintained code to fit my needs. I wouldn\u0026rsquo;t really need a full fledged rules engine or a special DSL, if the tool of choice is simple enough to use. Every DSL ties yourself to that very abstraction and getting rid of it will not only be a matter of the tool, but also getting rid of the way you think about the problem space you solved with the DSL.\nIt\u0026rsquo;s always a bit harder to change ones mind, but I assume from time to time it is essential to move forward.\nI already mentioned the tricky part when talking about my wishes for a good orchestration tool: fit my needs - simple to use. You might argue that a generic tool won\u0026rsquo;t be possible with such constraints. And I guess you\u0026rsquo;re right: there are reasons why our current setup still has its right to exist.\nThe Ugly # We have some special use cases on some services. Yep, exceptions to a generic pattern are evil, but without exceptions we would be quite bored, hm? Let\u0026rsquo;s dig into the interesting bit: in most cases, our platform needs to perform requests to several of our partners, external rating services, and document stores. As you might guess, there are some services which are not as fast as we\u0026rsquo;d prefer them to be. We actually have delays in the range of several minutes.\nThat\u0026rsquo;s why we need to consider long running requests during our continuous deployments. Using blue-green deployment, we have the option to deploy new releases of our service, use those for new incoming requests, but keep our old instances running for several minutes. That way pending connections won\u0026rsquo;t be cut during a certain timeout.\nCan you implement such a scenario with any orchestration tool? Well, I\u0026rsquo;m not aware of a simple tool to fit such needs. I consider Docker Swarm to be very simple to use, but it sadly won\u0026rsquo;t keep pending connections after service updates.\nReally? Well, not quite.\nA New Hope # Luckily we\u0026rsquo;re not the only ones to strive for zero downtime deployment. Some people out there use Spring Boot, our application environment, and some people use Docker. Some use both, just like us. Some want their servlet container to shut down gracefully (see spring-projects/spring-boot/4657), and some want to perform rolling upgrades with Docker (see moby/moby/30321).\nWe cannot expect from either Spring Boot or Docker, now also known as Moby, to generically keep pending connections and block a container shutdown.\nBut Brian Goff\u0026rsquo;s suggestion to handle the TERM signal inside our application made us dig a bit deeper into JVM shutdown hooks. Reading the Runtime Javadoc about addShutdownHook() made us wonder whether the JVM would accept our idea to block the shutdown for several minutes. In fact, you cannot simply ignore a statement like Shutdown hooks should also finish their work quickly. You never know if you don\u0026rsquo;t try, so we gave it a try and using Andy Wilkinson\u0026rsquo;s code snippet made it easy for us to create an example application.\nThe Awakening # Such an example application is available at gesellix/graceful-shutdown-spring-boot. Since we wanted to check a possible Docker Services setup as potential replacement for our Ansible based deployments, the example app can easily be deployed as a Docker Stack. If you\u0026rsquo;re not familiar with Docker Stacks, I recommend you to read the little introduction at docs.docker.com.\nYou can consider a Docker Stack to be the extended version of Docker Compose. The difference lies in the additional options to configure replicas, rolling update policies, and service constraints. The most important difference to Compose is probably the service deployment across several nodes. Everything is powered by Docker Swarm, so most of your knowledge about Swarm and Docker Services also applies to Stacks.\nBack to our example stack: it consists of Træfik as Docker aware reverse proxy and the example app as replicated Spring Boot service. To ease testing the shutdown hook timeout, a delay can be configured via Spring Boot mechanisms using the catalina.threadpool.execution.timeout.seconds property. Its value defaults to 30 seconds. Docker also needs to be aware of our delay, so that it won\u0026rsquo;t SIGKILL the service instances after 10 seconds. The necessary properties (stop-grace-period and update-config.delay) are already configured in the stack file with a value of 60 seconds.\nGet Real # Starting the full stack works like follows. Please start by cloning or downloading the gesellix/graceful-shutdown-spring-boot repository and change into the graceful-shutdown-spring-boot directory. You should find a stack.yml file in the repository root.\nIf you didn\u0026rsquo;t already initialize your swarm you\u0026rsquo;re going to need it now:\ndocker swarm init With the following command you\u0026rsquo;ll advice Docker to create a virtual network named traefik, download the necessary Docker images and create the configured services traefik and app. The stack will be named grace:\ndocker stack deploy --compose-file stack.yml grace You can follow the service logs with:\ndocker service logs -f grace_app When all services are running (you can check their current state via docker stack ps grace), please open your browser at http://localhost. You should see a little web page with two columns, each with a button. The left one allows you to check the basic connectivity by calling the app\u0026rsquo;s echo endpoint. The button in the right column allows you to simulate a long running request by generating an endless stream of random bytes. If you click that button, the blue area at the bottom should show an increasing number of received bytes. Click that button now.\nA service update with task downtime can be triggered e.g. by adding a new environment variable. Please use a new terminal window if you\u0026rsquo;re already following the service logs:\ndocker service update --env-add \u0026quot;foo=bar\u0026quot; grace_app Apart from the usual Spring Boot and Tomcat shutdown messages, the logs should emit messages like this one:\ngrace_app.1.ki669xys5x6x@moby | 2017-05-24 12:34:31.981 WARN 1 \u0026mdash; [ Thread-3] d.g.d.zerodowntime.GracefulShutdown : Context closed. Going to await termination for 30 SECONDS.\nDocker will randomly choose either grace_app.1 or grace_app.2, and your endless request should keep one of both delaying the shutdown for 30 seconds. After that delay a new log message should appear:\ngrace_app.1.ki669xys5x6x@moby | 2017-05-24 12:34:01.989 WARN 1 \u0026mdash; [ Thread-3] d.g.d.zerodowntime.GracefulShutdown : Tomcat thread pool did not shut down gracefully within 30 SECONDS. Proceeding with forceful shutdown\nSo, the application paused the shutdown for 30 seconds, and Docker didn\u0026rsquo;t forcefully kill the container (the application decided itself to allow the shutdown regardless of the ongoing requests). If you have a look at your browser window, the number of received bytes won\u0026rsquo;t increase anymore.\nFinal Destination # With that proof of concept I would assume that our services can be prepared for an orchestrator like Docker Swarm. I didn\u0026rsquo;t verify Kubernetes or DC/OS and check whether they support a similar option like stop-grace-period. I guess they have at least a similar concept.\nIn the end everything will be a container, but we won\u0026rsquo;t need to get in touch with it, because a tool like Docker Swarm handles the boring stuff for us.\nWe would only need to change our thinking from deploying containers to declaring service constraints.\nFeedback # How do you deploy your services? We would be interested to learn about alternatives, pros and cons, and maybe we could share our experience. You can contact me via Twitter @gesellix or get in touch with my team @EuropaceTech!\nIf you have suggestions for the demo app, please file an issue or create a pull request. Thanks!\n","date":"24. Mai 2017","permalink":"https://www.gesellix.net/posts/zero-downtime-deployment-with-docker-stack-and-spring-boot/","section":"Posts","summary":"","title":"Zero Downtime Deployment with Docker Stack and Spring Boot"},{"content":"The Docker InfraKit is a tool to create and manage you infrastructure in a declarative and self-healing manner.\nInfraKit itself more or less only consists of plugins, communicating via unix domain sockets.\nThere are different types of plugins, namely group, instance, and flavour, each with a focus on different layers of infrastructure management. Instance plugins are no surprise here: they manage your physical resources. Whether a physical resource is actually some machine or only a virtual concept, is an implementation detail of the instance plugin. Managing distinct instances would be unhandy, so you can use group plugins as a tool to operate on a group of instances with similar characteristics. The actual behaviour of your services on each instance of a group happens through flavour plugins. They define how to monitor services and which commands to run.\nInfraKit is still under heavy development, which is your chance to give the maintainers some feedback or suggestions so that the tool and the growing ecosystem of plugins will work for your use cases. It has been explicitly designed to manage your infrastructure, with the implication that it shouldn\u0026rsquo;t be regarded as replacement to e.g. Docker swarm mode.\nCreating your local InfraKit # I started with the tutorial you can find in the InfraKit repository. Over time, there have already been huge improvements and breaking changes to the commands and configuration, so that you should always verify if your local config does still work. Here is a list of commands to create the necessary binaries locally. I assume you have a working Golang environment:\nmkdir -p $GOPATH/src/github.com/docker cd $GOPATH/src/github.com/docker git clone git@github.com:/docker/infrakit.git cd infrakit make build-in-container After running the commands above you should have all binaries in a new subdirectory build:\n$ ls -l $GOPATH/src/github.com/docker/infrakit/build/ total 155720 -rwxr-xr-x 1 gesellix staff 7780048 Oct 20 21:31 infrakit -rwxr-xr-x 1 gesellix staff 8757616 Oct 20 21:31 infrakit-flavor-combo -rwxr-xr-x 1 gesellix staff 9774560 Oct 20 21:31 infrakit-flavor-swarm -rwxr-xr-x 1 gesellix staff 8654128 Oct 20 21:31 infrakit-flavor-vanilla -rwxr-xr-x 1 gesellix staff 8666928 Oct 20 21:32 infrakit-flavor-zookeeper -rwxr-xr-x 1 gesellix staff 8889808 Oct 20 21:31 infrakit-group-default -rwxr-xr-x 1 gesellix staff 9183136 Oct 20 21:32 infrakit-instance-file -rwxr-xr-x 1 gesellix staff 9308320 Oct 20 21:32 infrakit-instance-terraform -rwxr-xr-x 1 gesellix staff 8697344 Oct 20 21:32 infrakit-instance-vagrant Using InfraKit to perform deployments # I wanted to see how InfraKit feels when trying to use it as blue/green deployment utility. As mentioned above, that\u0026rsquo;s not exactly what InfraKit has been built for, but let\u0026rsquo;s see what happens.\nLet\u0026rsquo;s bootstrap a basic InfraKit environment by running some plugins. Exactly like described in the tutorial, I use the file instance plugin to create virtual instances represented as files. The default group plugin and vanilla flavour plugin are also used to make a local setup as easy as possible. So, let\u0026rsquo;s start the plugins as background tasks:\nbuild/infrakit-group-default \u0026amp; mkdir -p tutorial build/infrakit-instance-file --dir ./tutorial/ \u0026amp; build/infrakit-flavor-vanilla \u0026amp; The command build/infrakit plugin ls should respond with a list of the three mentioned plugins:\nPlugins: NAME LISTEN flavor-vanilla /Users/gesellix/.infrakit/plugins/flavor-vanilla group /Users/gesellix/.infrakit/plugins/group instance-file /Users/gesellix/.infrakit/plugins/instance-file You can find the unix socket file handles in the column LISTEN for each plugin. You can use the unix sockets to send commands to the plugins using a HTTP api. The InfraKit cli at build/infrakit makes that task a bit more convenient.\nYou can list the current instances by querying the instance plugin:\nbuild/infrakit instance --name instance-file describe We didn\u0026rsquo;t tell the instance plugin about any instances, yet, so the list should be empty. The group plugin accepts a new configuration in JSON format, either via STDIN or files. I created a subdirectory for those config files:\nmkdir -p tutorial-config Now let\u0026rsquo;s define a new group, called \u0026ldquo;app-blue\u0026rdquo;:\ncat \u0026lt;\u0026lt; EOF \u0026gt; tutorial-config/app-blue-group.json { \u0026#34;ID\u0026#34;: \u0026#34;app-blue\u0026#34;, \u0026#34;Properties\u0026#34;: { \u0026#34;Allocation\u0026#34;: { \u0026#34;Size\u0026#34;: 2 }, \u0026#34;Instance\u0026#34;: { \u0026#34;Plugin\u0026#34;: \u0026#34;instance-file\u0026#34;, \u0026#34;Properties\u0026#34;: { \u0026#34;Note\u0026#34;: \u0026#34;Instance properties app version 1.0\u0026#34; } }, \u0026#34;Flavor\u0026#34;: { \u0026#34;Plugin\u0026#34;: \u0026#34;flavor-vanilla\u0026#34;, \u0026#34;Properties\u0026#34;: { \u0026#34;UserData\u0026#34;: [ \u0026#34;docker run -dit -p 80:80 nginx:alpine\u0026#34; ], \u0026#34;Labels\u0026#34;: { \u0026#34;tier\u0026#34;: \u0026#34;web\u0026#34;, \u0026#34;project\u0026#34;: \u0026#34;app\u0026#34;, \u0026#34;colour\u0026#34;: \u0026#34;blue\u0026#34; } } } } } EOF You can recognize dedicated configuration blocks for the instance, and flavour plugins. The file instance plugin doesn\u0026rsquo;t do very much, but it will be labelled with the Note. The flavour plugin makes things actually happen by running an Nginx container listening on port 80 and configuring some labels. Since we\u0026rsquo;re describing a group here, it will need an ID, in this case I chose app-blue.\nThe configuration can now be passed to the group plugin, so that it registers a new group app-blue and passes the other configuration blocks to the chosen plugins:\nbuild/infrakit group watch tutorial-config/app-blue-group.json The group plugin also watches the new group for changes and automatically acts in a way to converge the actual state to the desired state. You can easily verify that behaviour by deleting one of the instances (by deleting a file in the subdirectory tutorial) and watching it re-creating another instance:\n$ ls tutorial/ instance-6050337735652914571 instance-8310575841542371925 $ rm tutorial/instance-6050337735652914571 INFO[5731] Adding 1 instances to group to reach desired 2 INFO[5731] Created instance instance-7932269456152577966 with tags map[infrakit.config_sha:l9lqpBGJSWIrDOtrliJRGSK1y60= infrakit.group:app-blue] $ ls tutorial/ instance-7932269456152577966 instance-8310575841542371925 The inspect sub-command gives you an overview on your group:\n$ build/infrakit group inspect app-blue ID LOGICAL TAGS instance-7932269456152577966 - infrakit.config_sha=l9lqpBGJSWIrDOtrliJRGSK1y60=,infrakit.group=app-blue instance-8310575841542371925 - infrakit.config_sha=l9lqpBGJSWIrDOtrliJRGSK1y60=,infrakit.group=app-blue Let\u0026rsquo;s add the app-green group:\ncat \u0026lt;\u0026lt; EOF \u0026gt; tutorial-config/app-green-group.json { \u0026#34;ID\u0026#34;: \u0026#34;app-green\u0026#34;, \u0026#34;Properties\u0026#34;: { \u0026#34;Allocation\u0026#34;: { \u0026#34;Size\u0026#34;: 2 }, \u0026#34;Instance\u0026#34;: { \u0026#34;Plugin\u0026#34;: \u0026#34;instance-file\u0026#34;, \u0026#34;Properties\u0026#34;: { \u0026#34;Note\u0026#34;: \u0026#34;Instance properties app version 2.0\u0026#34; } }, \u0026#34;Flavor\u0026#34;: { \u0026#34;Plugin\u0026#34;: \u0026#34;flavor-vanilla\u0026#34;, \u0026#34;Properties\u0026#34;: { \u0026#34;UserData\u0026#34;: [ \u0026#34;docker run -dit -p 81:80 nginx:alpine\u0026#34; ], \u0026#34;Labels\u0026#34;: { \u0026#34;tier\u0026#34;: \u0026#34;web\u0026#34;, \u0026#34;project\u0026#34;: \u0026#34;app\u0026#34;, \u0026#34;colour\u0026#34;: \u0026#34;green\u0026#34; } } } } } EOF build/infrakit group watch tutorial-config/app-green-group.json Nothing special here, I only changed some details and the port binding.\nPlease note that the current demo setup creates instances as files, but doesn\u0026rsquo;t actually run the Nginx containers.\nNow let\u0026rsquo;s assume that we want to deploy a new variant of our Nginx container, because we forgot to add a necessary environment parameter. You can either choose to modify the previously created tutorial-config/app-blue-group.json configuration or create a new one like this:\ncat \u0026lt;\u0026lt; EOF \u0026gt; tutorial-config/app-blue-group-2.json { \u0026#34;ID\u0026#34;: \u0026#34;app-blue\u0026#34;, \u0026#34;Properties\u0026#34;: { \u0026#34;Allocation\u0026#34;: { \u0026#34;Size\u0026#34;: 2 }, \u0026#34;Instance\u0026#34;: { \u0026#34;Plugin\u0026#34;: \u0026#34;instance-file\u0026#34;, \u0026#34;Properties\u0026#34;: { \u0026#34;Note\u0026#34;: \u0026#34;Instance properties app version 3.0\u0026#34; } }, \u0026#34;Flavor\u0026#34;: { \u0026#34;Plugin\u0026#34;: \u0026#34;flavor-vanilla\u0026#34;, \u0026#34;Properties\u0026#34;: { \u0026#34;UserData\u0026#34;: [ \u0026#34;docker run -dit -p 80:80 -e VERSION=2 nginx:alpine\u0026#34; ], \u0026#34;Labels\u0026#34;: { \u0026#34;tier\u0026#34;: \u0026#34;web\u0026#34;, \u0026#34;project\u0026#34;: \u0026#34;app\u0026#34;, \u0026#34;colour\u0026#34;: \u0026#34;blue\u0026#34; } } } } } EOF InfraKit won\u0026rsquo;t automatically apply those changes now, and you can ask it which actions it would execute when the new config would be applied:\n$ build/infrakit group describe tutorial-config/app-blue-group-2.json app-blue : Performs a rolling update on 2 instances InfraKit and blue/green deployment # How can InfraKit be used in a blue/green deployment? Since InfraKit knows your instances, groups, and flavours, it becomes easy for you to describe the desired state of your whole application setup. Similarly, applying changes becomes easy, too!\nBlue/green deployment is a concept to allow continuous deployment without interrupting your clients\u0026rsquo; interactions with your app. Your clients\u0026rsquo; don\u0026rsquo;t care about your deployment, in fact: they don\u0026rsquo;t want to be disturbed by technical details. So, in essence, blue/green deployment describes how to manage different versions of your app and switching between them with the help of a reverse proxy in front of them. Deployments are a multi-step process, where a new version is deployed as an \u0026ldquo;offline\u0026rdquo; version and afterwards the reverse proxy is reconfigured to use that fresh release instead of the old one.\nQuestion is: how do you perform your deployments? Some use shell scripts, some use tools originally designed for provisioning (e.g. Puppet or Ansible, which I described in another article), some might leverage modern orchestration tools. Well, the new kid is InfraKit and it could be used to perform your rolling deployments.\nAs you\u0026rsquo;ve seen above, performing a single deployment isn\u0026rsquo;t very hard, the only missing bit is the reverse proxy and some little mechanism to re-configure that proxy. But that\u0026rsquo;s the same solution for any other tool out there, maybe it\u0026rsquo;s integrated into your tool or you simply add a single shell command somewhere.\nThe significant value of InfraKit to me is: it behaves in a resilient way and it feels like a combination of cluster management and cron. Not in a way cron runs tasks which end after having finished their actions. If you know about regular puppet agent runs to regularly verify the desired state of your machines, you probably know what I mean. InfraKit takes your desired configuration once and then is your Watchmen for your whole group.\nYou have seen how InfraKit performs actions when your infrastructure doesn\u0026rsquo;t look like your desired infrastructure. You have also seen how it tells you about the actions it\u0026rsquo;s going to perform when changes would be applied. You can apply the changes now:\n$ build/infrakit group update tutorial-config/app-blue-group-2.json INFO[9857] Executing update plan for \u0026#39;app-blue\u0026#39;: Performs a rolling update on 2 instances INFO[9867] Scaler has quiesced INFO[9867] Found 2 undesired instances INFO[9867] Destroying instance instance-7932269456152577966 INFO[9871] Adding 1 instances to group to reach desired 2 INFO[9871] Created instance instance-5485092084438864736 with tags map[infrakit.config_sha:jHUgS68HiWOOPrTmbHy00-YV9QI= infrakit.group:app-blue] INFO[9877] Scaler has quiesced INFO[9877] Found 1 undesired instances INFO[9877] Destroying instance instance-8310575841542371925 INFO[9881] Adding 1 instances to group to reach desired 2 INFO[9881] Created instance instance-8719089717303567911 with tags map[infrakit.config_sha:jHUgS68HiWOOPrTmbHy00-YV9QI= infrakit.group:app-blue] INFO[9887] Scaler has quiesced INFO[9887] Finished updating group app-blue update app-blue completed InfraKit destroys and creates complete instances for you, which is probably a bit unusual when you think about your current deployment. You\u0026rsquo;re probably used to only stopping and creating containers, but not whole instances. The thing is: it depends on the instance plugin you choose. I\u0026rsquo;m not aware of a docker instance plugin or something more granular, but InfraKit would allow you to create one very quickly and specific for your needs.\nOutlook # What I wanted to show is another perspective on the tutorial, and show an option how to perform deployments. Since InfraKit is still in an early state there are some options missing - consider security management or notifications to the admin when InfraKit needs to react on an invalid state. Nevertheless there is a huge potential to improve the current state of infrastructure management and maybe even service deployment.\nThe idea behind InfraKit (and SwarmKit being available in the Docker engine) with consensus driven protocols and flexible plugins has a huge potential to make our lifes easier in the near future. Now is the time to start playing with the new concepts!\nYou should watch the Docker repos for new plugins, e.g. like the AWS plugin, or discuss with other adopters in the Docker Community #Infrakit Channel.\nP.S. # Destroying your infrastructure is maybe too easy:\nbuild/infrakit group destroy app-blue build/infrakit group destroy app-green ","date":"20. Oktober 2016","permalink":"https://www.gesellix.net/posts/blue-green-deployment-with-docker-infrakit/","section":"Posts","summary":"","title":"Blue/Green Deployment with Docker InfraKit"},{"content":"As maintainer for publicly available libraries one sometimes has to answer feature requests or handle feedback about missing features of the library. How does one prepare for different requirements? I\u0026rsquo;d like to tell you how and why I opted to implement yet another docker http client on my own and I\u0026rsquo;ll also try to explain where I see its benefits and disadvantages.\nnot invented here syndrome? # You have probably seen several Docker remote api client libraries for different environments or programming languages. During the development of the Gradle Docker plugin I had to choose how to communicate with the Docker remote api. The Java Docker API Client aka docker-java already existed, but wasn\u0026rsquo;t mature enough to simply use it for my needs. Other alternatives have been rare, so I decided to implement the remote api client on my own.\nUsing Groovy, it was quite convenient to communicate with the RESTful remote api. Implementing an own docker library helped to have fast development cycles because I didn\u0026rsquo;t have to wait for another library to add necessary features. I also relied on Groovy\u0026rsquo;s dynamic typing, so that I didn\u0026rsquo;t need to keep up with remote api changes.\nThe best example where the dynamic typing has been helpful is the create container endpoint with its port mappings and volume binds being configured in a JSON encoded request body. Groovy allowed me to implement such a payload as simple key/value map.\nwhere reality kicks in # Due to the low level nature of the remote api, including hijacking hacks on /attach, the Apache Http Client reached some limits. Even the Groovy HttpBuilder project hasn\u0026rsquo;t been very active during the last year, so that it wasn\u0026rsquo;t possible to use it with a more recent Http Client 4.3.x release. Similar dependency issues hit the docker-java library, e.g. dependency conflicts of the JAX-RS library when used in certain environments.\nRegarding the docker-java library, which provides a strongly typed and fluent api, we can observe a high amount of smaller maintenance tasks when new options are added to the Docker remote api. There are many pull requests which don\u0026rsquo;t add any logic, but only extend the api to pass parameters through to the actual http layer. There\u0026rsquo;s nothing wrong with that, but it\u0026rsquo;s another approach with advantages and disadvantages.\nSo which library would you choose? I only mentioned two libraries, but as you might guess there are other libraries available and they all have their useful niche. Nothing comes without tradeoffs, though.\nadjust and adapt # Instead of trying to work around problems I tried to remove them. I searched for alternatives of the Apache Http Client so that I could easily solve some of my issues with chunked responses or hijacked sockets. I didn\u0026rsquo;t find any alternatives, but why? I didn\u0026rsquo;t expect to be the first looking for a more flexible http client. After a while I gave up on finding a better http client library. It seemed like the Apache Http Client was a very good choice. Nevertheless I had problems with it.\nStill trying to remove problems, I considered implementing an http client on my own. Sounds stupid: there\u0026rsquo;s this little developer sitting at his desk, criticising a well-known and mature Apache library, but nonetheless thinking about implementing something better.\nI didn\u0026rsquo;t need many features like sophisticated connection pooling, and only wanted to perform simple requests with better control on response handling. The HttpURLConnection doesn\u0026rsquo;t provide a very convenient api, but it\u0026rsquo;s the highest level abstraction in the JDK which knows enough about HTTP without depending any external dependencies.\nReading about existing JDK features I learned about concepts like protocol handlers as combination of the URLConnection and a URLStreamHandler, and ContentHandlers. Such concepts helped me adding HTTP over unix socket support and reading Docker\u0026rsquo;s raw streams. Even HTTPS support was less complex than expected.\nThe current LowLevelDockerClient is probably not the most beautiful piece of code, but it does its job and I didn\u0026rsquo;t have any major issues with it yet. As usual, less code is good code and other mature http client libraries are probably just too big for my use case.\nwith less dependencies flexibility is improved # There\u0026rsquo;s nothing special with yet another http client, and I guess I have some luck that the docker-client library isn\u0026rsquo;t very popular compared to the docker-java library: with more users the potential to stumble on missing feature is increased a lot. Nevertheless I\u0026rsquo;m very satisfied with the result, because it\u0026rsquo;s the minimal amount of code necessary to talk to the Docker daemon, it\u0026rsquo;s less blown than a more generic library like JAX-RS, and it\u0026rsquo;s very focused on a lean workflow when it comes to modifying or extending the library.\nThis is not about bashing other bigger libraries. This is not about judgement which library is the better one. This is about use cases and certain contexts of other developers searching for a way to talk to an http enabled service.\nusers and their use cases # I see at least two groups of developers: one group likes to be led by an explicit api, which has the advantage that some low level aspects can be hidden from them. Such an api helps recognising breaking changes at compile time. As mentioned above, the advantages can also be disadvantages when it comes to maintainability: Docker is a moving target (and it moves fast), so a library talking to Docker needs to keep up with the pace, otherwise it won\u0026rsquo;t be able to satisfy its users\u0026rsquo; needs. Yet, this first group of developers probably doesn\u0026rsquo;t want to move so fast, which makes a less flexible library even more attractive to them.\nAnother group of developers likes it the bleeding edge way of life. They are the ones who try release candidates, read changelogs and the remote api documentation very carefully before going to bed, and don\u0026rsquo;t want a library to stand in their way.\nOne much anticipated Docker feature to mark containers with labels (instead of only container names) was only a simple new property in the create container request body\u0026hellip; at least from the remote api client\u0026rsquo;s point of view. How many lines of code needed to be changed in docker-java and docker-client, respectively? Well, in docker-client, I didn\u0026rsquo;t need to change any line. No single line. In docker-java, they needed to add about 34 lines of code, which ultimately only changes a single field.\nAgain, this is not about a battle of libraries! This is about different design decisions and their effects on maintenance and user\u0026rsquo;s code. The main difference to me lies in the way a library allows a user to use new features.\nOne library passes data from the user to the actual endpoint. The other library needs a new release before a user can send enhanced data to the endpoint. The first library doesn\u0026rsquo;t care about the data, the second library doesn\u0026rsquo;t even allow one to bypass its api. The first library doesn\u0026rsquo;t check or validate the data, while the second library removes a huge amount of potential errors just by design.\nYou see: there\u0026rsquo;s no simple good or bad. There\u0026rsquo;s only a user with a use case and some constraints or expectations. If you expect a library to protect yourself from fundamental errors in your code, you\u0026rsquo;ll often have to accept foreign contraints. If you only need a simple library to pass your data to another system and only handle low level stuff for you, then you should be prepared that some aspects need to be solved on your side.\nFor me, simplicity is very important. Libraries might be popular, mature, and feature rich. But they need to stay simple. Otherwise complexity and restrictions can creep into the consumers\u0026rsquo; code which also increases the risk of breaking changes. Too much simplicity can lead to niche existence, which can be optimal for a small use case, but can minimize innovation due to less challenges from different consumers.\nsummary # I wouldn\u0026rsquo;t say that one single library for a use case is enough. It\u0026rsquo;s good to have different solutions and the chance to evaluate each. I only mentioned two Docker client libraries explicitly, but you can find at least four different ones being actively developed. The same applies to Maven Docker plugins or Gradle Docker plugins. Since I mentioned my own Gradle Docker plugin above, I would also like you to have a look at another nice plugin. Fun fact: it internally uses the docker-java library.\nComparing similar libraries or frameworks should be made easier, though. It\u0026rsquo;s very hard to find the best solution in similar libraries, when every library seems to have all necessary features. Usually, the most popular choice won\u0026rsquo;t be too wrong. But is it the best solution for your own needs?\nIn my experience, minimizing dependencies was a good choice, even for the tradeoff of implementing something from scratch. I also prefer to keep an api as less restrictive as possible, but I see a lot of use cases where an explicit api can help. Having a good documentation (be it as a test suite or as prose in a wiki) helps newcomers to not be frustrated in the first five minutes of contact with a library.\n","date":"11. Juli 2015","permalink":"https://www.gesellix.net/posts/choosing-a-library/","section":"Posts","summary":"","title":"On Library Dependencies and API Evolution"},{"content":"You have heard about Continuous Delivery or Continuous Deployment, don\u0026rsquo;t you? Apart from abstract definitions we showed you how one can perform deployments with the help of Gradle, Ansible, and Docker. A quite complete example project has been published at GitHub.\nMost deployment pipelines contain the steps in the figure below. The order of creating deployables and performing tests doesn\u0026rsquo;t really matter, both steps might even be performed asynchronously.\nBut do deployments end with the newly released application being available on the production server? Unfortunately not: using an automated deployment pipeline comes with other consequences.\nDeployment Doesn\u0026rsquo;t End on Prod # You\u0026rsquo;ll need to ensure that there aren\u0026rsquo;t any breaking changes on different layers. For example the database should support both new and old applications as client. The application itself should be capable of serving older clients like another service or an older browser instance of the frontend. Even the frontend code in the browser needs to be updated at any point in time. How would you ensure the user doesn\u0026rsquo;t perceive updates in a bad way?\nThen, how do you as developer or service provider know about the health of your application? How do you know about the users accepting or ignoring your shiny new features? Who wakes you at night, when your application doesn\u0026rsquo;t behave like it should?\nThere\u0026rsquo;s more to DevOps # Even without a very clear definition of DevOps - maybe there\u0026rsquo;s a broad agreement on its properties - you might have experienced that it helps breaking some barriers between the Ops and Dev silos. With a team of developers and most of them having an affinity to operational tasks you certainly don\u0026rsquo;t need operators to deploy your application anymore. Operations can shift their tasks to become an infrastructure provider, probably even deciding to delegate most of their tasks to AWS or DigitalOcean.\nBut the developers now need to understand their own aplications, databases, network, storage, or even hypervisor to a higher degree. They need to learn how database maintenance looks like. Developers need to understand which implications it has to increase the deployment frequency, which part of a system fails first in cases of higher load, and how the users\u0026rsquo; behaviour has an impact on application availability.\nDevelopers can learn from operations (while also teaching them e.g. about infrastructure as code) and they should. Why? Because it\u0026rsquo;s their application and they know it best. There\u0026rsquo;s little value in waking up one of the operators only to have them call a developer at night.\nSo What? # The point is: Developers need to take responsibility for their whole application, including its database and big parts of its infrastructure. They continuously deploy an application, so they need to handle every aspect of the pipeline and a production environment. Tools like Ansible and Docker make it easy for them, but tools are only valuable when the user understands them.\nWith such a responsibility in mind, developers can design an architecture which is flexible and resilient to temporary issues or environmental aspects. An architecture being designed by developers has a higher chance to be accepted by themselves. Apart from working to only get money, people want to be motivated. Motivation comes with fun and I guess a happy developer is a better developer.\nBack to the deployment pipelines. And where they end. They don\u0026rsquo;t end on production - sorry.\nWhat Comes After Prod? # A responsible developer wants to sleep well. They don\u0026rsquo;t sleep well after the successful deployment on prod. They only sleep well when they can be sure that if something bad happens, they\u0026rsquo;ll be notified.\nHow do they know if everything is ok? They introduce monitoring. They add feature toggles. They design their services backwards compatible and they make their endpoints resilient to timing issues or errors of other services. They don\u0026rsquo;t only automate a deployment, but they automate every other aspect, too.\nMonitoring and Alerting # Monitoring helps collecting metrics about service behaviour and health. Simple service availability checks with tools like Pingdom are a good start. Monitoring over a time range allows to infer what\u0026rsquo;s normal and what\u0026rsquo;s an incident: you don\u0026rsquo;t need too much artificial intelligence when finding out in which range an average memory consumption lies.\nAlerting based on the collected metrics becomes simple: observe behaviour over one or two weeks, define rules which allow some fuzziness, and trigger alerts when your rules are violated. Alerts can be managed by services like PagerDuty, or simply sent via email, tools like HipChat, or even Twitter.\nYou certainly don\u0026rsquo;t need to build yet another monitoring or alerting system - there are lots of them out there. Well known tools like Nagios and Icinga are still valid and stable, but if you\u0026rsquo;re looking for fresh concepts, you might want to try Prometheus. It comes with everything you\u0026rsquo;ll need: so-called exporters to expose host or application metrics, a server to collect and store them, and an alertmanager to handle rule violations. With an upcoming release of the alertmanager you might even try to automatically recover issues by triggering generic webhooks.\nThough especially the alertmanager isn\u0026rsquo;t declared as stable enough to use it right now as an infrastructure\u0026rsquo;s backbone, there\u0026rsquo;s high potential with the overall concept behind it.\nFeature Toggles? User Feedback! # Apart from maintaining a running system and observing its healthyness, there\u0026rsquo;s some value in developing new features. Ah, well, features are the reason why your users want to use your service!\nBut how do you handle bigger features, which need to be implemented over several days and which aren\u0026rsquo;t finished by only one single deployment? Do you stop your deployment pipeline until the feature is complete? How would you quickly deploy bug fixes then? When using feature branches, how do you continuously perform tests and canary deployments? When do you consider a feature to be complete?\nOne way of handling half baked features is to deploy them to production, but not enabling them. Feature toggles are a way of disabling them. You can even partially enable them to only a small part of your users - do you have beta testers?\nWhen continuous deployment is about fast feedback cycles, don\u0026rsquo;t forget the user! They give you valuable feedback. You decide how they contact you: as a surprised user who is angry about a bug or as someone who knows that you\u0026rsquo;re able to quickly fix bugs (maybe still angry, though).\nTell the user about continuous delivery and build a community. Just like operations and developers tear down barriers, you can embrace your users and integrate them as early as possible into your development cycle.\nFeature toggles help you to hide unfinished code, but adding an easy way to submit feedback integrates your users.\nSummary # Changing a culture to apply DevOps methods has consequences. Developers don\u0026rsquo;t only write software anymore, but are on call. They probably have user contact. Whoo! That doesn\u0026rsquo;t mean life to become harder, but it\u0026rsquo;s the chance to embrace change. Change is good!\nDevelopers need to perform operative tasks like monitoring and they need to handle user feedback. Continuous delivery forces people, teams, and enterprises to think about new ways to handle incidents, implementing new features, and talking to the user.\nFeedback cycles don\u0026rsquo;t end with the tests or health checks. A complete pipeline integrates user feedback. So the figure above needs to be fixed and can look like this:\nThere is no end of your deployment pipeline. Only when you close your service or when there isn\u0026rsquo;t any user. 😉\n","date":"29. Mai 2015","permalink":"https://www.gesellix.net/posts/where-continuous-deployment-ends/","section":"Posts","summary":"","title":"Where Continuous Deployment ends"},{"content":"You might want to find out if a Docker container named \u0026ldquo;elasticsearch\u0026rdquo; is currently running. There is a docker ps command to list all running containers in a table-like view. Lets assume there are two containers currently running, the result would look like this: Ignoring the fact that it\u0026rsquo;s a quite wide table you might want to take the chance and use some good old tools. Yes, the classics like grep, awk, sed and the other usual suspects. You only have to find a line with the last column to equal or contain \u0026ldquo;elasticsearch\u0026rdquo;.\nThat\u0026rsquo;s not very exciting. A hipster developer these days would prefer to use JSON, because\u0026hellip; JSON. Luckily, when using Docker we can use the Docker Remote API to perform the same request the docker ps command did. Combined with a great tool like jq you can try to get the same result. So, the first step would be to ask the Docker Remote API to list the running containers:\n$ curl -X GET \u0026#34;http://172.17.42.1:2375/containers/json?all=false [ { \u0026#34;Command\u0026#34;: \u0026#34;bash\u0026#34;, \u0026#34;Created\u0026#34;: 1410901503, \u0026#34;Id\u0026#34;: \u0026#34;be2dd8aa82220a3b90a8712f47cfbbd482dbde7abd01f8c646bc3443a9ea04ab\u0026#34;, \u0026#34;Image\u0026#34;: \u0026#34;ubuntu:14.04\u0026#34;, \u0026#34;Names\u0026#34;: [ \u0026#34;/silly_elion\u0026#34; ], \u0026#34;Ports\u0026#34;: [], \u0026#34;Status\u0026#34;: \u0026#34;Up 2 seconds\u0026#34; }, { \u0026#34;Command\u0026#34;: \u0026#34;/elasticsearch/bin/elasticsearch\u0026#34;, \u0026#34;Created\u0026#34;: 1410889877, \u0026#34;Id\u0026#34;: \u0026#34;5250a96ebd16e72e664f31814f4a5a03df1ab7b50c52044f6a623002acca1ee0\u0026#34;, \u0026#34;Image\u0026#34;: \u0026#34;elasticsearch:latest\u0026#34;, \u0026#34;Names\u0026#34;: [ \u0026#34;/elasticsearch\u0026#34; ], \u0026#34;Ports\u0026#34;: [ { \u0026#34;IP\u0026#34;: \u0026#34;0.0.0.0\u0026#34;, \u0026#34;PrivatePort\u0026#34;: 9200, \u0026#34;PublicPort\u0026#34;: 9200, \u0026#34;Type\u0026#34;: \u0026#34;tcp\u0026#34; }, { \u0026#34;IP\u0026#34;: \u0026#34;0.0.0.0\u0026#34;, \u0026#34;PrivatePort\u0026#34;: 9300, \u0026#34;PublicPort\u0026#34;: 9300, \u0026#34;Type\u0026#34;: \u0026#34;tcp\u0026#34; } ], \u0026#34;Status\u0026#34;: \u0026#34;Up 3 hours\u0026#34; } ] We want to filter the Names array and test if one of both arrays from both containers equals our name \u0026ldquo;/elasticsearch\u0026rdquo;. The leading slash is an internal detail to Docker, which is usually removed by the native Docker client.\nSo, we extract the Names property from both objects and check whether they equal our desired name. This is the intermediate result:\n$ curl -X GET \u0026#34;http://172.17.42.1:2375/containers/json?all=false\u0026#34; \\ | ./jq \u0026#39;.[].Names | .[] | . == \u0026#34;/elasticsearch\u0026#34;\u0026#39; false true We get two results, for each container, and each Names entry. But we wanted a single result, which tells us if one of all containers is the eleasticsearch container. So, let\u0026rsquo;s combine the list to an array and reduce its entries to a single value:\n$ curl -X GET \u0026#34;http://172.17.42.1:2375/containers/json?all=false\u0026#34; \\ | ./jq \u0026#39;[ .[].Names | .[] | . == \u0026#34;/elasticsearch\u0026#34; ] \\ | reduce .[] as $item (false; . | $item)\u0026#39; true Note the [...] around the filter of the previous step, which combines both results to a single array with two entries.\nWell, the command doesn\u0026rsquo;t look very simple, but I would say by explicitly using JSON properties it\u0026rsquo;s far more stable than greping a pseudo-table. I also found the jqplay playground for checking the jq filter to be a big help. Just give it a try!\n","date":"16. September 2014","permalink":"https://www.gesellix.net/posts/determine-whether-a-docker-container-is-running/","section":"Posts","summary":"","title":"Determine whether a Docker container is running"},{"content":"Sometimes you need to apply a change on all of your CouchDB documents. I actually needed to remove an old and unused property from all of them.\nCouchDB conceptually doesn\u0026rsquo;t allow you to update a document without knowing its revision, so you would end up reading all your documents, modify them and update them one by one. Sounds nice, eh?\nupdate handlers # There\u0026rsquo;s a better way, though. CouchDB has a concept of Document Update Handlers, which are saved in the database\u0026rsquo;s design document and are accessible through the HTTP API. The update handler takes a document id and can perform any modification on the referenced document. On success, the changes will be saved as an updated (or newly created) document.\nSo, in my case, I created an updated handler function removeProperty like this:\n{ \u0026#34;_id\u0026#34;: \u0026#34;_design/myDatabase\u0026#34;, \u0026#34;_rev\u0026#34;: \u0026#34;2-316a27933f7ab3ab72ab537b9651c267\u0026#34;, \u0026#34;views\u0026#34;: { ... } }, \u0026#34;updates\u0026#34;: { \u0026#34;removeProperty\u0026#34;: \u0026#34;function(doc, req) {delete doc[\\\u0026#34;myProperty\\\u0026#34;];return [doc, toJSON(doc)];}\u0026#34; } } As you see, I only need two instructions:\ndelete doc.myProperty return the updated document as first element in the array, to tell CouchDB that it has to save my changes. What\u0026rsquo;s missing is how to call the update handler for every document in the database.\niterating over all document ids # First, I got all documents by GETing the /_all_docs resource on the database. The response contains an array of all documents with the ids and revisions. I needed to extract the document ids, so the very convenient jq JSON processor came into my mind. It allows you to filter any JSON and is very handy when using it in scripts or on the command line. Applying it to the /_all_docs response looked like this:\n$ curl -X GET \u0026#34;http://couchdb.dev:5984/database/_all_docs\u0026#34; | ./jq \u0026#39;.rows | .[].id\u0026#39; ... lots of ids here... \u0026#34;29888e1753601f9c96c757d5429aa1eb\u0026#34; \u0026#34;29888e1753601f9c96c757d5429ab10d\u0026#34; \u0026#34;29888e1753601f9c96c757d5429ac061\u0026#34; \u0026#34;29888e1753601f9c96c757d5429acb02\u0026#34; \u0026#34;29888e1753601f9c96c757d5429ad587\u0026#34; \u0026#34;29888e1753601f9c96c757d5429ae3ec\u0026#34; \u0026#34;_design/myDatabase\u0026#34; Now I only needed to strip the quotes \u0026quot; and remove the design document from the list and call my update handler for each entry. The resulting one liner looks a bit long, but does it\u0026rsquo;s job:\n$ curl -X GET \u0026#34;http://couchdb.dev:5984/database/_all_docs\u0026#34; | ./jq \u0026#39;.rows | .[].id\u0026#39; | sed -e \u0026#39;s/\u0026#34;//g\u0026#39; | sed -e \u0026#39;s/_design.*//g\u0026#39; | xargs -I id curl -X PUT \u0026#34;http://couchdb.dev:5984/database/_design/myDatabase/_update/removeProperty/id\u0026#34; Using xargs with the option -I \u0026lt;anything\u0026gt; allows you to replace \u0026lt;anything\u0026gt; on your command with the current argument, in this case the document id.\nSince I implemented the update handler to return the document as JSON, all updated documents had been returned as a response. The complete update results in many requests to the CouchDB, but I didn\u0026rsquo;t notice any problems or performance issues.\nCouchtato # My colleagues at Hypoport showed me an alternative tool named Couchtato. It\u0026rsquo;ll sadly need a bit more setup as only the usual command line tools, but it makes the update look a bit more beautiful than the long and cryptic command line above. I won\u0026rsquo;t go into detail here, everything is explained in the official readme.\n","date":"30. Juli 2014","permalink":"https://www.gesellix.net/posts/bulk-update-of-couchdb-documents/","section":"Posts","summary":"","title":"Bulk update of CouchDB documents"},{"content":"Deploying products to production is a quite individual process for every application. Building deployment pipelines has already been described on a high level of abstraction, e.g. by the ThoughtWorks guys Jez Humble, Dan North and Chris Read in their paper The Deployment Production Line, not to forget the more general theme about Continuous Delivery being described in its own book by Jez Humble and David Farley.\nYou might search for tools implementing the according patterns, and you\u0026rsquo;ll find some like ThoughtWorks Go (it\u0026rsquo;s free!) or Continuous Integration tools like TeamCity or Jenkins enabling you to describe build chains with several build steps.\nSome aspects of your build pipeline aren\u0026rsquo;t implemented in such tools, though, because they cannot model your individual needs. So you end up writing shell scripts or other \u0026ldquo;low level\u0026rdquo; descriptions of your deployment details. That\u0026rsquo;s no problem per se, but this is where you also have to think about how to maintain your deployment and infrastructure code.\nfill the gap # Infrastructure as code is one aspect maintaining and stabilizing your infrastructure - you certainly know Puppet or Chef addressing your needs, and there\u0026rsquo;s a massive increase of similar tools like SaltStack and Ansible that can be used on top or instead of the established tools.\nSince you cannot draw a clear line where your infratructure ends and where your application begins, you probably have some shell scripts quite near your application code base. Such scripts or build tools with their plugins handle tasks like running tests, packaging of libraries and static resources, publishing artifacts to repositories, downloading them to test and production servers, stopping and redeploying your application or other services and probably configuring a router to make new requests reach the new release.\nOver time you add some more services participating your deployments, and you\u0026rsquo;ll end up with some more shell scripts, some more plugins, some more configuration, some more aspects to keep in mind. But this is probably not always fun.\nYou need to orchestrate. This can be fun, but it\u0026rsquo;s also a job to be done.\nhave fun # How would you have more fun maintaining your deployments? There\u0026rsquo;s no general answer. Let\u0026rsquo;s face it, it\u0026rsquo;s your individual deployment. But reading articles or books and exchanging ideas can improve your and other solutions, provide new aspects or even tools.\nWe would like to share our experience about deploying a non-trivial Spring Boot application including an AngularJS frontend, talking with several other services and being implemented with a growing DevOps culture in mind.\nOur build tool of choice is Gradle, we use TeamCity as CI server and Gradle scripts to orchestrate test infrastructure and deployments, while our application is packaged in Docker images. We can say that \u0026ldquo;it just works\u0026rdquo;™ and it definitely makes fun.\nIn order to give you an idea how such a setup works for us, we\u0026rsquo;ve started a little series on our employer\u0026rsquo;s IT blog at Hypoport. Please check back from time to time to read about details, but also to use the chance to give feedback. Feel free to ask questions on unclear subjects and suggest other topics, so we can write about according details. You can use the comments section on the IT blog or get in contact via Twitter @gesellix.\nSo, go ahead to the first article about our Continuous Deployment Pipeline with Gradle and Docker with an overview of our setup and stay tuned for updates!\n","date":"27. Juli 2014","permalink":"https://www.gesellix.net/posts/have-fun-with-gradle-and-docker/","section":"Posts","summary":"","title":"Deploying with Gradle and Docker: have fun"},{"content":"Probably a bit late, but private life is more important :)\nYou can find the slides of my talk for the Gradle Summit 2014 online at http://gesellix.github.io/gradle-summit-2014/. The video ist available at YouTube along with the other Gradle Summit videos.\nIn case you\u0026rsquo;re interested in details regarding our implementation of a deployment pipeline using Gradle and Docker (2nd part of the Gradle Summit talk), please follow this blog or my employer\u0026rsquo;s IT blog. If you\u0026rsquo;re near Germany in November, you might consider visiting the Continuous Lifecycle 2014 conference where I\u0026rsquo;m going to talk about our pipeline in more detail.\n","date":"3. Juli 2014","permalink":"https://www.gesellix.net/posts/gradle-summit-2014-follow-up/","section":"Posts","summary":"","title":"Gradle Summit 2014 follow-up"},{"content":"In line with our deployment pipeline written in Gradle and using Docker, we currently use Groovy\u0026rsquo;s process execution methods to talk with a command line Docker client.\nThat way we make ourselves dependent to an installed Docker client on our CI servers. Since we don\u0026rsquo;t like to provide a bunch of specific CI servers, I started to implement a HTTP client for Docker, written in Groovy. The reason not to use existing Java implementations of such a Docker client was simply timing: some months ago the now completely rewritten Java Docker API Client wasn\u0026rsquo;t so well maintained than today\u0026hellip; and, well, I like to play with new tools and wanted to explore the Docker remote API for myself.\nUsing a Java API would be one possible solution to rewrite our Gradle scripts, but in order to implement a higher level interface on top of the HTTP wrapper I wanted to use Gradle plugin mechanisms and a simple task configuration.\nSo here they are: the inevitable duo of a Docker-Client written in Groovy and a Gradle-Docker-Plugin - along with a small Gradle example project.\nPlease have a look at the projects, give them a try and if you find any missing feature or bug, please create an issue at one of the projects linked above! I would also be glad about feedback, either via Twitter @gesellix or by mail (tobias @ gesellix.de). Thank you!\n","date":"3. Juli 2014","permalink":"https://www.gesellix.net/posts/gradle-docker-plugin-and-docker-client-available/","section":"Posts","summary":"","title":"Gradle-Docker-Plugin and Docker-Client available"},{"content":"Keepass-Node now supports ssl/https. You only need to provide your server key and certificate and browse the KeePass-Node instance via https://....\nAnother feature to make KeePass-Node sync with a Google Drive file has been added, too. Currently there\u0026rsquo;s only read support.\nPlease see the updated readme for details and configuration.\n","date":"9. Mai 2014","permalink":"https://www.gesellix.net/posts/keepass-node-with-https-support/","section":"Posts","summary":"","title":"Keepass-Node with https support"},{"content":"Great news: I\u0026rsquo;m going to speak at the Gradle Summit 2014 in Santa Clara. This will be the very first talk I\u0026rsquo;ll give in public so I\u0026rsquo;m quite excited!\nMy talk will have two parts, with the first one about how we migrated a Maven multi-module project of 300 pom.xml files to Gradle. The second part will show you how we currently use Gradle in combination with Docker to continuously deploy a product of the EUROPACE 2 platform to production.\nYou might occasionally check my employer\u0026rsquo;s blog where my team and other co-workers also post some related stuff. Details regarding the Gradle Summit talk are available at the Schedule.\n","date":"6. April 2014","permalink":"https://www.gesellix.net/posts/gradle-summit-2014/","section":"Posts","summary":"","title":"Gradle Summit 2014"},{"content":"Using Docker on a Puppet managed host influences Facter when it tries to find the host ip address.\nWhich network interface is the best? # Facter collects some so called facts about the system and provides them to Puppet modules. When using a fact like :ipaddress you\u0026rsquo;ll see that Facter only uses the output of the native ifconfig command, sorts all existing interfaces by name and takes the first non local interface as result. Facter then sets :ipaddress, :macaddress and :netmask as provided by that interface.\nMost Linux based systems use eth0 as interface name for the public interface. This seems to be ok in most cases, but after installing Docker you\u0026rsquo;ll see an additional interface docker0. Facter now reads the network configuration from the Docker interface, which can lead to problems in your Puppet modules.\nThe same problem also arises without Docker when you have more than one interface installed. Which interface would you Facter expect to use, eth0 or eth1?\nTelling Facter the truth # Confronting your favourite search tool with the described behaviour (you\u0026rsquo;re never the first person having your problem, don\u0026rsquo;t ya?) should link you to the PuppetLabs Q\u0026amp;A page about the issue. The given answer provides three options to overcome the problem:\nFile a bugreport Write a custom fact ipaddress_primary that returns your desired value Use ipaddress_eth0 instead of ipaddress Filing a bugreport is probably not a good idea, since Facter cannot know enough about which interface you expect. Using the dedicated ipaddress_eth0 should be valid, when you can be sure that the interface name is static, e.g. by setting it in your Puppet modules. Sadly, I couldn\u0026rsquo;t use the third option, because I had different interface names on different servers.\nJust for the record: there\u0026rsquo;s at least a fourth option: manually set Docker\u0026rsquo;s interface name so that it appears after eth0 in the sorted list of interfaces. Well, sometimes you can\u0026rsquo;t simply change interface names, and I preferred to make Facter work as expected without changing the world around it.\nOverriding facts # So I tried to write a custom fact - the documentation helped a lot and it\u0026rsquo;s quite easy. I had the idea of overriding the built in :ipaddress fact, but that doesn\u0026rsquo;t seem to be possible by using custom facts. Luckily, PuppetLabs have provided a way of overriding facts by the use of environment variables: instead of adding custom facts, you can set a variable like FACTER_ipaddress=\u0026quot;192.168.178.42\u0026quot;.\nFacter accepts any fact name after the prefix FACTER_, so you can override any fact you like. In my case the ip addresses are static, so this is a very convenient way of making Factor work together with Docker. When using dynamic ip addresses, you should check if any of the other suggestions above work for you.\nYou can manage /etc/environment with Puppet, so the environment variables are available without manually editing files on a server. My /etc/environment now looks like this:\nFACTER_ipaddress=\u0026#34;192.168.178.42\u0026#34; FACTER_netmask=\u0026#34;255.255.255.0\u0026#34; Happy shipping!\n","date":"2. März 2014","permalink":"https://www.gesellix.net/posts/facter-docker-and-the-public-ip-address/","section":"Posts","summary":"","title":"Facter, Docker and the public ip address"},{"content":"After making myself familiar with Docker I wanted to use it on a more recent Ubuntu 13.10 release. I still didn\u0026rsquo;t install Docker natively on my pc, but use a Vagrant box to play with fresh releases. After downloading a fresh Ubuntu 13.10 image, creating a VirtualBox image and installing the current Docker release 0.8.0, I tried to use my other little toys CouchDB and Elasticsearch in Docker containers. Sadly, I couldn\u0026rsquo;t connect to the exposed ports anymore.\nThe Docker installation docs for Ubuntu Linux 13.04 and 13.10 mention the configuration of UFW to modify the DEFAULT_FORWARD_POLICY to accept all traffic. Well, the UFW wasn\u0026rsquo;t enabled. So the usual digging began, searching on StackOverflow, GitHub and developer blogs. Most hints mentioned disabling the IPv6 support via sysctl, but a quite clear statement of Jérôme Petazzoni at a StackOverflow answer made me search for alternatives.\nInstead of disabling IPv6 some people found a way to enable IPv6 inside of containers: Andreas Neuhaus showed some native lxc commands to make containers support IPv6. Marek Goldmann also shows some other use cases for native lxc commands to connect containers on multiple hosts. Well, hopefully a pull request to make Docker natively support IPv6 will merged soon. My problem didn\u0026rsquo;t look like missing IPv6 support inside the container, though.\nSome debugging, bridge configuration, iptables fun and interface setups later I came back to the sysctl config and had a look at the existing entries. You might by surprised how well each default entry is documented and especially one entry looked promising: I enabled IPv6 forwarding on all interfaces by uncommenting the entry with: net.ipv6.conf.all.forwarding=1 in the /etc/sysctl.conf. A quick reboot later finally showed a response from the dockerized CouchDB. Relax! Sometimes it\u0026rsquo;s too easy.\nNow comes the fun part with reconnecting the CouchDB River Plugin for ElasticSearch over different containers again :)\n","date":"16. Februar 2014","permalink":"https://www.gesellix.net/posts/docker-and-ipv6-on-ubuntu-13-10-saucy-salamander/","section":"Posts","summary":"","title":"Docker and IPv6 on Ubuntu 13.10 (Saucy Salamander)"},{"content":"When using the Gradle war plugin you\u0026rsquo;re enabled to declare providedCompile dependencies to tell the compiler to include those dependencies in the compile classpath, but to not make Gradle include them in the packaged .war artifact. Your build.gradle script might look like this, e.g. for a webapp using GWT:\napply plugin: \u0026#39;war\u0026#39; dependencies { ... providedCompile \u0026#39;com.google.gwt:gwt-user:2.5.1\u0026#39; ... } The dependency shown in the example has some own dependencies, which can be shown by using the :dependencies task. You should get a dependency tree like the snippet below:\n... providedCompile - Additional compile classpath for libraries that should not be part of the WAR archive. \\--- com.google.gwt:gwt-user:2.5.1 +--- javax.validation:validation-api:1.0.0.GA \\--- org.json:json:20090211 ... You might now want to have some Validation in your application, so you\u0026rsquo;d like to use the validation-api as already provided through the transitive dependency of gwt-user. Since Gradle won\u0026rsquo;t package provided dependencies, you might declare the validation-api with a compile scope, so that your build script looks like follows:\napply plugin: \u0026#39;war\u0026#39; dependencies { ... compile \u0026#39;javax.validation:validation-api:1.0.0.GA\u0026#39; providedCompile \u0026#39;com.google.gwt:gwt-user:2.5.1\u0026#39; ... } Well, when now calling the Gradle build task, and deploying the created .war artifact you\u0026rsquo;ll notice that the validation-api hasn\u0026rsquo;t been packaged. An explanation can be found at a Gradle forum thread telling you to disable transitive dependencies for cases like shown above. The solution for the example above would look like this:\napply plugin: \u0026#39;war\u0026#39; dependencies { ... compile \u0026#39;javax.validation:validation-api:1.0.0.GA\u0026#39; providedCompile (\u0026#39;com.google.gwt:gwt-user:2.5.1\u0026#39;) { transitive = false } ... } Please read the linked forum thread completely for details!\n","date":"2. Januar 2014","permalink":"https://www.gesellix.net/posts/providedcompile-and-compile-dependencies-with-gradle/","section":"Posts","summary":"","title":"ProvidedCompile and Compile Dependencies with Gradle"},{"content":"Just in case you\u0026rsquo;re searching for a way to use multiple compiler arguments in your Gradle builds, have a look at this snippet:\nsubprojects { tasks.withType(JavaCompile) { options.compilerArgs \u0026lt;\u0026lt; \u0026quot;-Xlint:unchecked\u0026quot; \u0026lt;\u0026lt; \u0026quot;-Xlint:deprecation\u0026quot; } } I took the example from stackoverflow, but you can find details at the Gradle DSL for the JavaCompile class.\n","date":"16. Dezember 2013","permalink":"https://www.gesellix.net/posts/using-compiler-arguments-with-gradle/","section":"Posts","summary":"","title":"Using Compiler Arguments with Gradle"},{"content":"As mentioned at the AngularJS blog, Internet Explorer 8 support will be dropped with the upcoming 1.3 release of AngularJS. Dropping support means that the AngularJS developers won\u0026rsquo;t test their releases on IE8 anymore, but they won\u0026rsquo;t actively remove any code so chances are that one might still use AngularJS 1.3.x on IE8. I hope not. Hopefully Google and others will increase pressure on other companies to upgrade their infrastructure to current Windows (or even Linux, Mac) systems with current browser technologies. Well, it might even be ok to completely leave Internet Explorer and use Chrome, Firefox or Safari. Maybe Microsoft manages to update their browser more often and with more recent features than they did in the past. IE10 was a huge step forward, so let\u0026rsquo;s see. Just in case you need some background or statistics about the importance of IE8, read about it at a quite thorough blog article.\n","date":"15. Dezember 2013","permalink":"https://www.gesellix.net/posts/angularjs-1-3-will-drop-ie8-support/","section":"Posts","summary":"","title":"AngularJS 1.3 will drop IE8 support"},{"content":"As you might have noticed, AngularJS 1.2.2 1.2.3 has been released lately, without dedicated announcement. Our update from an AngularJS 1.2-rc2 has been quite smooth, only two hints might be noteable in addition to the official migration guide.\nWith the current version the AngularJS team has fixed some issues regarding the isolate scope as described in the changelog for the 1.2.0 release or at the relevant GitHub issues #1924 and #2500. In directive tests you might currently use the isolate scope of an element using the notion element.scope(). With the current release you have to use the newly introduced function element.isolateScope(). Just a simple find and replace task :-)\nDon\u0026rsquo;t forget to check third party libraries for compatibility updates. We used the Angular-UI Select2 directive in an older release 0.0.2. Running our e2e-tests produced some strange and non-deterministic error messages in PhantomJS, but not in Chrome or other standard browsers. The errors seemed to be triggered due to directive priority changes in Angular 1.2.0, so the update to the current release 0.0.4 of ui-select2 made the errors go away by setting a fixed priority.\n","date":"27. November 2013","permalink":"https://www.gesellix.net/posts/angularjs-1-2-update-hints/","section":"Posts","summary":"","title":"AngularJS 1.2 update hints"},{"content":"The Joomla! TitleLink and Shortlink extension documentation can be found at http://gesellix.github.io/titlelink/ and http://gesellix.github.io/shortlink/.\nCleaning my homepage also meant cleaning up content, so I prefer not to have several copies of the same document in different places. Old links probably still refer to this homepage, so please update your links to use the official location. Thanks!\nDue to changed focus away from Joomla, but towards Node.js, AngularJS and other tools of that environment, i\u0026rsquo;ve been quite inactive on the Joomla extensions. Another extension named Mailman subscription module is only available in the GitHub source repository and didn\u0026rsquo;t get a dedicated project page.\nBut I still have Joomla on my watch list which also means that I don\u0026rsquo;t like to stop supporting users of TitleLink or the other extensions. So you can at least use the issue tracker when something doesn\u0026rsquo;t work as expected. You can find the project pages at the following urls:\nTitleLink Shortlink Mailman ListSubscribe ","date":"15. November 2013","permalink":"https://www.gesellix.net/posts/titlelink-and-shortlink-documentation/","section":"Posts","summary":"","title":"TitleLink and Shortlink Documentation"},{"content":"A Node.js with AngularJS based implementation of a KeePass2 browser is available at GitHub.\nWhat? # You should probably know about KeePass as a tool to manage your passwords or other secrets in an encrypted file. Since the default tool to edit and view your passwords is based on .NET you might not be able to use your keys everytime you need them due to missing libraries or a wrong platform (Mono needs to be installed on Linux systems).\nKeePass-Node is based on the idea of BrowsePass, which helps you to open a file from your current filesystem or from a URL by using only browser based libraries. To make BrowserPass more convenient, I didn\u0026rsquo;t want to always upload a keepass file. I just wanted to enter a password to see my entries, so I searched for an improved solution. Long story short: I didn\u0026rsquo;t find any, so I build it myself. KeePass-Node was born.\nUsage # The installation is described in the README.md file. You\u0026rsquo;ll only need a Node.js on your server, the rest should work out of the box. Some features are a bit rough on the edge and you still need to install a SSL proxy in front of the keepass-node server in order to use the HTTPS protocol.\nI didn\u0026rsquo;t give it a try yet, but I\u0026rsquo;d like to use keepass-node as provider for browser plugins like passifox. I guess it should work, but if you already have some experience with such plugins, don\u0026rsquo;t hesitate to contact me.\nFeedback # I would be glad about feedback, either via Twitter @gesellix or by using the issue tracker at GitHub. Thanks!\n","date":"13. November 2013","permalink":"https://www.gesellix.net/posts/node-js-with-angularjs-implementation-of-a-keepass2-browser/","section":"Posts","summary":"","title":"Node.js with AngularJS implementation of a KeePass2 browser"},{"content":"Playing around with docker running inside a Vagrant VM and trying to use some services being exposed via HTTP ports makes you ask how to automatically forward the exposed docker ports through Vagrant to the host system. phew\nI found an already merged pull request, which looked like what I wanted. So, after a peek into the docker Vagrant file I tried to expose a CouchDB port to the host system like this:\n$host\u0026gt; FORWARD_DOCKER_PORTS=\u0026#39;true\u0026#39; vagrant up $host\u0026gt; vagrant ssh $vagrant\u0026gt; docker run -d -p 49815:5984 -i -t gesellix/docker-example $host\u0026gt; curl -X GET \u0026#34;http://localhost:49815\u0026#34; It just worked! If you\u0026rsquo;d like to have a look at my example project, you can find it at GitHub. I\u0026rsquo;m going to continue building a complete application backend including CouchDB, ElasticSearch and other toys. You might have better docker or Vagrant knowledge than me, so if you\u0026rsquo;d have any hints, I\u0026rsquo;d really like some feedback. Thanks!\n","date":"5. November 2013","permalink":"https://www.gesellix.net/posts/vagrant-forwarding-ports-exposed-by-docker/","section":"Posts","summary":"","title":"Vagrant Forwarding Ports Exposed by Docker"},{"content":"A recently published article shows our solution to initialize an AngularJS application using promises. You might have similar problems when your application initialization depends on http responses or other ansynchronous tasks.\nAn online demo can be found at jsfiddle.net and the code is available at GitHub.\n","date":"28. Oktober 2013","permalink":"https://www.gesellix.net/posts/initialization-of-an-angularjs-application-with-asynchronous-data/","section":"Posts","summary":"","title":"Initialization of an AngularJS Application with Asynchronous Data"},{"content":"Recently I stumbled over a tool Coveralls. It helps monitoring your test coverage. You can integrate it e.g. with your Travis-CI builds by using a plugin for your favorite build tool. Needless to say that they also provide Travis-CI similar badge to show off your current test coverage near your current build status in your README file at GitHub.\nTheir docs sadly aren\u0026rsquo;t very focused on Java tools like Maven or Gradle, so you have to find a working plugin on your own (or code a new one by using the Coveralls API). I tried the coveralls-gradle-plugin for my gradle-debian-plugin and it works like a charm! Just have a look at the list of results - showing some potential to improve test coverage ;-)\n","date":"27. Oktober 2013","permalink":"https://www.gesellix.net/posts/using-coveralls-in-your-gradle-build/","section":"Posts","summary":"","title":"Using Coveralls in your Gradle build"},{"content":"Those who know my old web site might wonder what gesellix.net is about.\nWell, gesellix.de wasn\u0026rsquo;t very frequently updated with new content or posts and I guess it was in parts due to the Joomla! CMS being too much distraction when writing just a little blog post. Joomla is fine as CMS, I still like it for the massive amount of extensions and for the great community behind it. Yet, Ghost makes creating content much easier for me. So I decided to use a Ghost based blog as my new primary channel to publish ideas, projects or random thoughts.\nMy old web site will be online for a while, at least as long as I support my Joomla extensions TitleLink and Shortlink.\n","date":"22. Oktober 2013","permalink":"https://www.gesellix.net/posts/gesellix-net-is-now-the-blog/","section":"Posts","summary":"","title":"gesellix.net is now 'the' blog"},{"content":"A new Gradle plugin for creating Debian compatible packages (.deb) is available at Bintray. It allows you to package any files through a convenient Gradle build script configuration into a Debian package compatible file. You may also include your MavenPublications (.jar or .war) by only referring to their publication names. To use the plugin you need some knowledge about the package structure and configuration scripts. The Debian New Maintainers\u0026rsquo; Guide, chapters 4 and 5, will help a lot.\nThe current release v10 can be included in your Gradle buildscript dependencies. A minimal example is available in the readme file. The tests in the GitHub repository show you more complete examples on how to use the plugin to create a Java webapp package.\nI\u0026rsquo;m still adding more tests to improve stability, but you may already start using it. Please give me some feedback or ask questions at the GitHub issue tracker!\n","date":"20. Oktober 2013","permalink":"https://www.gesellix.net/posts/gradle-debian-plugin/","section":"Posts","summary":"","title":"Gradle Debian plugin"},{"content":"Playing around with node.js made me build a monitoring tool to perform continuous health checks.\nI have an application running with a status servlet available under a URL like http://example.com/healthcheck. For a healthy system, I get a JSON as response including an attribute result.summary. In case that summary isn\u0026rsquo;t OK, I want the monitor to send me an email with details.\nNothing spectacular, so you\u0026rsquo;ll find the little piece (of currently untested) code at GitHub. Have fun!\n","date":"17. Oktober 2013","permalink":"https://www.gesellix.net/posts/node-js-monitor/","section":"Posts","summary":"","title":"Node.js Monitor"},{"content":"This blog is about development topics, with a focus on software development in Java and JavaScript. Recently used frameworks and tools are the omnipresent ones like Spring, Elasticsearch, Node.js and AngularJS, but I also like to play with other well known tools like Wicket and CouchDB.\nYou may find me on the internet on several platforms like GitHub using my alias \u0026ldquo;gesellix\u0026rdquo;. As an employee at Hypoport AG in Berlin I sometimes blog at their IT blog. There you\u0026rsquo;ll find some exciting content of my colleagues, too! The best way to contact me would be at Twitter.\n","date":"15. Oktober 2013","permalink":"https://www.gesellix.net/posts/welcome/","section":"Posts","summary":"","title":"Welcome"},{"content":"The Fest Assertions library, a fluent Java API for writing assertions in your test or production code, is quite popular in my development team. Because of it's clean and fluent interface, it was no question to me when trying to use it in an Android project. But due to it's dependency on java.awt classes and due to Android's missing support for them, I currently cannot use it for writing tests in my Android codebase.\nThere might be several solutions to solve such an incompatibility, like adding classes providing missing packages in Android. Another solution is breaking the fest assertions library into several modules, so that one can use only Android compatible fest assertion modules. The library fest-assert is currently being refactored at GitHub, so nothing hinders us from forking and giving modularization a chance.\nThe whole approach is quite easy:\nidentify all awt dependent classes and corresponding resources in artifact A and move them to another artifact B. for all commonly used classes between artifacts A and B, move them to a third artifact C, so that both A and B can be dependent on C. in order to ease dependency management for users, create another artifact X, which itself depends on A and B. Usually, most users should only depend on artifact X, which gives them most of the commonly used assertions. For special needs, you just drop your dependency on artifact X and replace it with one or more of the smaller modules. Using the example above, we have the following modules available:\nA: fest-assert-core (https://github.com/gesellix/fest-assert-2.x): contains all core assertions, abstract base classes and the main API, but without dependencies on java.awt classes.\nB: fest-assert-awt (https://github.com/gesellix/fest-assert-awt-2.x): contains all awt assertions, which have simply been moved from the old core assertion library.\nC: fest-test (https://github.com/gesellix/fest-test): contains commonly used test classes. Some classes have been moved from the fest-assert-core artifact, to make them available for fest-assert-awt, too.\nX: fest-assert (https://github.com/gesellix/fest-assert): A minimal artifact without Java classes, providing only dependencies on both fest-assert-core and fest-assert-awt. This artifact should be used in most cases, so that fest developers could continue modularization of fest-assert-core without breaking any client code. fest-assert should be used as facade to all commonly used assertion artifacts.\nThe only breaking change is a new factory class for the new awt specific artifact, in this case you would use \"AwtAssertions\" instead of \"Assertions\". You can find an example using both artifacts in on test class in the fest-examples repository (see the class MixedAssertionFactoriesTest). The whole modularized code can be found at the provided links, a discussion about this approach can be found at the fest easytesting developer group.\nAfter discussion, I hope all changes find their way into the main repository at https://github.com/alexruiz/ and further modularization will take place to make Fest Assertions compatible with GWT and other frameworks. In case you want to support or improve this approach, please provide feedback at the google group or at GitHub!\n","date":"12. August 2012","permalink":"https://www.gesellix.net/posts/modularization-of-the-fest-assertions-library/","section":"Posts","summary":"","title":"Modularization of the Fest Assertions library"},{"content":"Die meisten Beiträge sind auf Englisch.\n","date":null,"permalink":"https://www.gesellix.net/de/posts/","section":"Beiträge","summary":"","title":"Beiträge"},{"content":"","date":null,"permalink":"https://www.gesellix.net/de/","section":"gesellix.net","summary":"","title":"gesellix.net"},{"content":"Liebe Deutsche Bahn AG,\nseit Jahren bin ich regelmäßiger Nutzer Ihrer Infrastruktur. Sei es anfangs für regelmäßige Wochenend-Pendelei, später die S-Bahn in Berlin und in letzter Zeit mehr oder weniger regelmäßige Fahrten zwischen Berlin und Westerwald.\nBahnfahrten sind für mich eine entspannte Möglichkeit Entfernungen zu überbrücken, ohne großartige Checkins und Wartezeiten auf Gepäckbänder über mich ergehen zu lassen. Trotz meiner Liebe zum Autofahren sehe ich die Bahn auch als vorteilhaft an, wenn es um Treibstoffkosten, Autoversicherung und KFZ-Steuer geht. Mal ganz abgesehen vom Wertverfall eines Autos. Ja, die Umwelt freut sich bestimmt auch, aber da fehlen mir neutrale Statistiken.\nGrundsätzlich gehe ich also mit einem guten Willen zum Bahnhof und steige in den Zug. Die Regel sähe so aus, dass ich von A nach B reise. Abfahrt und Ankunft stehen auf dem Ticket oder meiner Reiseverbindung und das mit dem Umsteigen bekomme ich auch noch hin. Da ich häufig genug fahre, lohnt sich eine BahnCard 50, ganz nebenbei kann ich so auch noch Bonuspunkte sammeln und werde dadurch sogar mit Freikarten oder netten Kleinigkeiten belohnt.\nAlles wunderbar.\nWenn da nicht die Ausnahmen wären.\nAb diesem Punkt werde ich etwas detaillierter über meine Erwartungen schreiben. Allerdings wäre das langweilig, also muss ich betonen, dass es sich um enttäuschte Erwartungen handelt. Möglicherweise sind meine Erwartungen auch illusorisch, in diesem Fall würde ich Sie bitten, mein Weltbild wieder näher zur Realität zu rücken. Zunächst beschreibe ich jedoch die Realität, die ich in letzter Zeit häufiger wahrnehme. Dazu fange ich exemplarisch einfach da an, wo ich mich für eine Reise entschieden habe und auf der Internetseite der Bahn nach passenden Verbindungen suche.\nDie Internetseite ist meines Erachtens recht einfach zu bedienen. Einige Kleinigkeiten könnte man bestimmt optimieren, aber grundsätzlich findet man schnell eine passende Verbindung und kann sie nach einem Login buchen, das Online-Ticket ausdrucken und ist glücklich. Ich reserviere meistens auch gleich die nötige Anzahl an Sitzplätzen, um sicherzustellen, dass ich neben meiner Begleitung sitzen kann und vielleicht sogar an einem Tisch sitze. Im Rahmen der Buchung muss ich mich einloggen. Die entsprechende Maske enthält natürlich zwei Felder für Benutzername und Passwort, direkt darunter erscheint auch noch ein Button, der den Login durchführt. Nun ist es so, dass ich es gewöhnt bin, meine Hände auch nach Eingabe von Benutzername und Passwort an der Tastatur zu lassen. Also drücke ich Enter, in der Erwartung, dass dadurch der Login ausgeführt wird.\nEnttäuschung Nummer 1: Der Druck auf Enter löst nicht den Login aus, sondern den auch noch irgendwo auf der Seite erscheinenden Link für den Fall, dass ich mein Passwort vergessen habe. Wieso? Weil Sie herausgefunden haben, dass die Mehrzahl Ihrer Benutzer ihr Passwort vergessen? Oder vielleicht, weil Sie davon ausgehen, dass man jeden Button nur mit Hilfe der Maus bedient? Oder liegt es daran, dass die Verbreitung von Pads und Touchscreens so dermaßen zugenommen haben, dass man sowieso kein Enter mehr drückt?\nVorschlag Nummer 1: Bitte prüfen Sie, wie oft jemand von der Loginseite im Rahmen einer Buchung plötzlich auf die Passwort-Vergessen-Seite geht und sein Passwort dann doch nicht anfordert. Sollte die Conversion-Rate auf der Passwort-Vergessen-Seite ungewöhnlich niedrig sein, überdenken Sie doch einfach mal den Wert des submit-Attributs auf der Login-Seite. Ich schlage \"Login\" als neuen Wert vor. Macht ja in dem Kontext irgendwie Sinn.\nIch muss zugeben, dass ich in die genannte Falle immer seltener tappe, man ist ja lernfähig. Wir können die Buchung also als durchgeführt betrachten und warten schon gespannt auf die Abreise.\nMit Hilfe der genannten Reservierung weiß ich sicher, in welchem Waggon welchen Zuges an welchem Gleis ich einsteigen muss und kann sogar mit Hilfe der Sitzplatznummer am Bahnhof entscheiden, ob ich besser vorne oder hinten am Waggon einsteige. Toll.\nEnttäuschung Nummer 2: \"Bitte beachten Sie die umgekehrte Wagenreihenfolge\" ist wohl eine der häufigsten Ansagen, die ich seit diesem Jahr zu hören bekommen habe. Anders formuliert: ich stehe am Bahnsteig genau an der Stelle, die am weitesten von dem Waggon entfernt ist, in dem ich meinen Sitzplatz habe. Warum ist das so? Weil in der Mitte entweder ein Bordrestaurant oder die 1. Klasse oder sonstwas ist. Die 2. Klasse sitzt in der Regel eher am Ende des Zuges. Das sieht man wunderbar auf den entsprechenden Anzeigen/Plakaten, die recht genau Auskunft über die Waggonreihenfolge eines Zuges geben. Man liest dort also ab, wo man am Bahnsteig dem gewünschten Waggon möglichst nahe kommt, schaut nochmal kurz auf die elektronische (aktuellere) Anzeige am Bahnsteig und stellt sich an die angegebene Position. Die oben genannte Ansage holt einen wieder auf den Boden der Tatsachen zurück. Lustig, wenn man die Bahnreisenden mal von oben betrachtet, wie sie hektisch vom Anfang zum Ende des Bahnsteigs (und umgekehrt) rennen. Wenn man selbst mit Koffer, Kind und Co. rennen muss, eher weniger lustig. Das Problem ist aber nicht die ausnahmsweise vorkommende falsche Wagenreihenfolge, sondern eher die Tatsache, dass es die Regel zu sein scheint.\nVorschlag Nummer 2: Wenn die umgekehrte Wagenreihenfolge so häufig vorkommt, wäre es dann nicht viel einfacher, die Pläne zu ändern? Das kostet bestimmt ein bisschen Papier, aber im Verhältnis zur Kundenzufriedenheit ist das wohl nur ein unwesentlicher Kostenfaktor. Bitte erstellen Sie Pläne, die der Realität möglichst nah sind. So schwer kann das nicht sein und Ihre Kunden werden es Ihnen vielleicht nicht danken, aber sie werden sicherlich weniger schimpfen.\nWunderbar. Ich habe nun also meinen Platz gefunden und nichts zu tun, außer gelegentlich mein Ticket dem Zugbegleiter zu zeigen und aufzupassen, dass ich rechtzeitig aussteige. Der Zug nimmt Fahrt auf.\nPlötzlich werden wir langsamer. Mitten im traumhaften Stillleben Deutschlands kommen wir zum Stillstand. Schöne Landschaft, leider hatte ich keinen Landschaftsurlaub gebucht und somit auch nicht besonders viel Sinn für einen solchen Genuss. Da kommt auch schon die Durchsage des Zugführers, ich zitiere sinngemäß eine typische Meldung: \"Wir haben einen außerplanmäßigen Halt. Grund ist eine Baustelle.\" Die Tatsache, dass es sich um einen außerplanmäßigen Halt handelt, hätte ich ohne die Durchsage bestimmt nicht bemerkt.\nScherz beiseite: war die Baustelle etwa überraschend? Ich kann in der heutigen Zeit einfach nicht glauben, dass man von einer Baustelle auf einer stark befahrenen Strecke nichts weiß, bis man mit dem Zug kurz davor steht. Eine Baustelle wird eingeplant. Es werden Termine festgelegt, wann eine Baustelle die Züge beeinträchtigen darf und wie lange die Aktion dauern darf. Es gibt strenge Regeln, wie sich Baustellenarbeiter auf Gleisen verhalten dürfen, es werden aktuelle Fahrpläne berücksichtigt etc. Auf der Seite der Bahn findet man sogar ein Verzeichnis, hübsch auf Landkarten ersichtlich, wann und wo welche Strecke beeinträchtigt sein könnte - zur Bedienbarkeit äußere ich mich lieber nicht. Die Bahn ist also in der Lage, Baustellen im Voraus einzuplanen und sogar dem Kunden auf einer speziellen Seite Informationen zu präsentieren.\nEnttäuschung Nummer 3: ich wurde nicht zum Zeitpunkt der Buchung meines Tickets auf die Baustelle hingewiesen, obwohl das erstens keinen besonderen Aufwand darstellen dürfte (ich habe immerhin einen konkreten Zug mit konkretem Datum und Uhrzeit gebucht) und zweitens viel zielgerichteter gewesen wäre, als von mir zu verlangen, dass ich jetzt anhand der Zugnummer den korrekten Fahrplan suche und auf der komischen Baustellenübersichtsseite die Strecke auf potentielle Hindernisse überprüfe. Ich bin Kunde, ich habe auf so etwas keine Lust und manchmal auch gar nicht die Zeit, mich mit solchen Dingen zu beschäftigen. Ich will genau dort über die mich betreffenden Dinge informiert werden, wo ich es brauche.\nVorschlag Nummer 3: So wie ich aktuell schon kurz vor der Buchung auf aktuell bekannte Verspätungen eines Zuges hingewiesen werde, oder wo mich die Bahn mit Hinweisen auf eine starke Nutzung eines Zuges hinweist (mit dezenter Empfehlung für eine kostenpflichtige Reservierung), möchte ich bitte auch sehen, wie die zu erwartende Fahrtzeit ausfällt. Konkret: auf der Reise von Berlin nach Montabaur fahre ich normalerweise über Frankfurt mit einer Umsteigezeit von ca. einer halben Stunde (laut Plan). Wenn die Bahn schon vorher weiß, dass diese halbe Stunde wegen der Baustelle mit absoluter Sicherheit nicht zu erwarten ist, dann will ich das auch wissen! Seien Sie doch bitte ehrlich und realistisch Ihren Kunden gegenüber.\nNachdem ich die Blätter auf dem Baum draußen durchgezählt habe, geht's weiter. Der Zug nimmt komischerweise diesmal keine volle Fahrt auf. Mir schwant nichts Gutes.\nDa kommt auch schon die Durchsage, die uns Fahrgäste auf einen vor uns liegenden Zug hinweist. Er scheint einen Defekt zu haben, dessen Behebung nicht so einfach zu sein scheint. Zumindest wird eine recht schwammige Aussage über die zu erwartende Pause geäußert. Die Fahrgäste müssen sich also in Geduld üben und wir bleiben wieder stehen. Langsam entwickeln sich diverse Lösungsansätze für diese Verspätung. Unter anderem ganz klassisches Marschieren. Ja, zu Fuß.\nWir stehen noch, aber wohl zwecks Fahrgastunterhaltung wird die nächste Durchsage durch den Äther verteilt. Inhaltlich kaum Neuigkeiten, abgesehen von der etwas konkreteren Aussage, dass die Pause länger dauern wird als in der ersten Durchsage noch angenommen. In diesen Momenten ist es mir eine Freude die verschiedenen Gesichter der anderen Fahrgäste zu beobachten. Man kann fast jede Facette einer menschlichen Stimmung erkennen. Von blankem Hass über abgestumpftes Hingeben seines Selbsts ins Schicksal bis hin zu übersprungartigem wahnsinnigem Grinsen ist alles dabei. Nur kein Verständnis für die arme Deutsche Bahn AG.\nEnttäuschung Nummer 4: Zu viele Züge sind zu oft defekt. Wen wundert's? Keine Ahnung, mich nicht. Wenn das Unternehmen im hauseigenen Magazin großspurig anpreist, dass jetzt auch die ICs mit neuem Interieur ausgestattet werden, dann weiß man im Umkehrschluss, dass das dafür notwendige Kapital nicht zur Instandhaltung der Fahrttüchtigkeit von Zügen verwendet wird. Das allseits beliebte Thema \"Berliner S-Bahn im Winter\" möchte ich hier nicht vertiefen.\nVorschlag Nummer 4: Bitte setzen Sie andere Prioritäten, wo Sie Ihr Kapital einsetzen. Meines Erachtens ist es viel geschäftsschädigender, wenn Kunden regelmäßig verspätet ankommen, als dass die Kunden in älteren Sitzen ihre Fahrt genießen.\nFaszinierenderweise geht die Fahrt nun recht exakt nach der prognostizierten Dauer weiter. Wenn doch nur die Fahrpläne so verlässlich wären...\nIch komme total entspannt in Frankfurt an. Insgesamt ca. 45 Minuten Verspätung haben dafür gesorgt, dass ich meinen Anschlusszug nicht mehr erreicht habe. Wie erwähnt: ich hatte ursprünglich ca. 30 Minuten Umsteigezeit, die ich schon zum Zeitpunkt der Buchung als Verspätungspuffer betrachtet hatte. Allerdings betrachte ich eine Verzögerung von 10 Minuten noch als Verspätung, alles andere ist schlichtweg unprofessionell und geschäftsschädigend.\nWarum haben Bahnkunden eigentlich diese Engelsgeduld? Und warum bin ich selbst so unbelehrbar, der Bahn immer wieder eine neue Chance zu geben?\nAch ja: ich hatte noch nicht explizit erwähnt, dass die beschriebene Fahrt zwar so stattgefunden hat, aber nicht als einzigartig einzustufen ist. Ähnliche Fahrten mit leichten, aber letztlich unwesentlichen Abweichungen in den konkreten Begründungen für eine Verspätung hat es Zuhauf gegeben. Ich finde ja Klasse, dass ich als Kunde ganz genau weiß, warum ich gerade wieder zu spät bin. Obwohl... es gibt manchmal auch Durchsagen à la \"Wir sind verspätet. Grund ist eine Verspätung.\" Informativ ist anders. Dennoch kann man Ihre Bemühungen erkennen, dass Sie auf den Kunden zugehen möchten. Bitte versuchen Sie dann auch mal, sich in den Kunden hinzuversetzen. So schwer kann das nicht sein, reisen Sie einfach mal mit dem Zug. Am besten mehr als einmal und vielleicht sogar über verschiedene Strecken. Sie sparen dabei übrigens Treibstoff und müssen nicht lange an Gepäckbändern rumstehen.\nEs gibt abgesehen von der Reise bis Frankfurt natürlich noch einen kleinen Epilog. Die Bahn bietet Ihren Kunden die Möglichkeit, ein Fahrgastrechte-Formular auszufüllen, in dem der Kunde beschreiben kann, wann mit welchem Zug er verspätet oder gar nicht angekommen ist. Grundsätzlich erhält der Kunde dann bei Verspätung ab 60 Minuten noch 25% seines Fahrkartenpreises zurück. Ab einer Verspätung von 120 Minuten sind's dann sogar ganze 50%. Bei meiner Reise waren es natürlich nur 117 Minuten.\nVorschlag Nummer 5 (die passende Enttäuschung dazu sollte beim Leser dieser Zeilen angekommen sein): Ab 30 Minuten Verspätung sind 25% des Ticketpreises fällig, ab 60 Minuten 50% und ab 90 Minuten bekommt der Kunde den vollen Fahrkartenpreis erstattet. Klingt hart? Das war noch nicht der komplette Vorschlag. Ich bin außerdem dafür, dass die Bahn jedem einzelnen Fahrgast die entsprechende Entschädigung innerhalb von 10 Tagen auf das entsprechende Konto überweist. Nichts mit Gutschein oder anderen kundenbindenden Maßnahmen. Schlicht und ergreifend Geld zurück. Für jeden mit Ticket. Unabhängig davon, ob der Kunde tatsächlich gefahren ist oder nicht. Für eine vertraglich vereinbarte aber nicht gelieferte Leistung hat der Kunde doch hoffentlich noch ein Recht auf Rückerstattung. Alles andere ist Humbug. Klingt immer noch hart? Ja. Kundenzufriedenheit ist leider hart zu erkämpfen und ich bin der Ansicht, dass es Unternehmen gibt, die sich diesem Kampf stellen sollten. Falls der Ansatz zu teuer ist, schlage ich vor dort zu kürzen, wo es am wenigsten weh tut: am Wasserkopf. Einfach mal Druck ablassen. Hat man bei den alten Dampfloks auch so gemacht.\n","date":"13. Juli 2012","permalink":"https://www.gesellix.net/de/posts/liebe-deutsche-bahn-ag/","section":"Beiträge","summary":"","title":"Liebe Deutsche Bahn AG"},{"content":"Working with external interfaces often needs a secured connection, mostly implemented using SSL. I recently ran into some fresh setup of two hosts, sending each other SOAP requests via HTTPS.\nBeing a Java developer at my office, I got some error messages quite unknown to me, but after some reading of blogs and with other informational sources the problems had been fixed. Understanding the concept behind SSL and how the SSL handshake works are crucial when trying to understand error messages and to know where to fix them.\nIn case you are trying to use SSL connections with certificates in combination with Java you may have a look at a little how-to I wrote at my employers Hypoport IT Blog. It\u0026rsquo;s written in German, but I guess you can understand most parts. Most of its content is taken from another article, enhanced with additional tips and solutions for special issues.\n","date":"15. Februar 2012","permalink":"https://www.gesellix.net/posts/java-ssl-configuration/","section":"Posts","summary":"","title":"Java SSL Configuration"},{"content":"1. Verantwortlicher # Verantwortlich für die Datenverarbeitung auf dieser Website im Sinne der Datenschutz-Grundverordnung (DSGVO):\nTobias Gesellchen Ürziger Straße 8 53175 Bonn Deutschland E-Mail: tobias@gesellix.de\n2. Überblick # Diese Website ist ein privates Blog. Es gibt keine Benutzerkonten, keine Kommentarfunktion, kein Kontaktformular, keine Cookies, kein Tracking und keine Analysewerkzeuge. Schriften, Skripte und die Suche werden von dieser Website selbst ausgeliefert, nicht von Drittanbietern.\nPersonenbezogene Daten fallen nur in zwei Fällen an: technisch bedingt beim Aufruf der Seiten (Server-Logdateien beim Hosting-Anbieter) und wenn Sie mir eine E-Mail schreiben.\n3. Hosting und Server-Logdateien # Diese Website wird bei Netlify, Inc., 101 2nd Street, San Francisco, CA 94105, USA, gehostet. Beim Aufruf einer Seite verarbeitet Netlify automatisch Daten, die Ihr Browser übermittelt, insbesondere:\nIP-Adresse Datum und Uhrzeit der Anfrage aufgerufene Seite bzw. Datei Referrer-URL (die zuvor besuchte Seite) Browsertyp und -version sowie Betriebssystem Diese Daten sind technisch erforderlich, um die Website auszuliefern und ihre Sicherheit und Stabilität zu gewährleisten. Eine Zusammenführung mit anderen Datenquellen oder eine Auswertung zu anderen Zwecken findet durch mich nicht statt.\nRechtsgrundlage: Art. 6 Abs. 1 lit. f DSGVO. Mein berechtigtes Interesse liegt in der sicheren und fehlerfreien Bereitstellung der Website.\nAuftragsverarbeitung: Netlify verarbeitet diese Daten in meinem Auftrag. Der dafür erforderliche Vertrag nach Art. 28 DSGVO (Netlify Data Processing Agreement) ist Bestandteil der Vertragsbedingungen von Netlify.\nÜbermittlung in die USA: Netlify ist nach dem EU-U.S. Data Privacy Framework zertifiziert. Die Übermittlung erfolgt damit auf Grundlage des Angemessenheitsbeschlusses der EU-Kommission (Art. 45 DSGVO). Weitere Informationen finden Sie in der Datenschutzerklärung von Netlify.\n4. Lokale Speicherung im Browser # Wenn Sie das Erscheinungsbild (hell/dunkel) umschalten, speichert die Website diese Einstellung im lokalen Speicher (Local Storage) Ihres Browsers. Die Einstellung verlässt Ihr Gerät nicht und wird nicht an mich oder Dritte übertragen. Sie können sie jederzeit über die Einstellungen Ihres Browsers löschen.\nRechtsgrundlage: § 25 Abs. 2 Nr. 2 TDDDG, da die Speicherung unbedingt erforderlich ist, um die von Ihnen ausdrücklich gewünschte Funktion bereitzustellen.\n5. Kontakt per E-Mail # Wenn Sie mir eine E-Mail schreiben, verarbeite ich Ihre Angaben (E-Mail-Adresse, Name falls angegeben, Inhalt der Nachricht), um Ihre Anfrage zu beantworten. Die Daten gebe ich nicht weiter und lösche sie, sobald sie für die Bearbeitung nicht mehr erforderlich sind, sofern keine gesetzlichen Aufbewahrungspflichten bestehen.\nRechtsgrundlage: Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse an der Beantwortung von Anfragen), bei Anfragen im Zusammenhang mit einem Vertrag Art. 6 Abs. 1 lit. b DSGVO.\n6. Externe Links # Beiträge enthalten Links zu anderen Websites, zum Beispiel GitHub, Mastodon oder YouTube. Erst wenn Sie einem solchen Link folgen, werden Daten an den jeweiligen Anbieter übertragen. Für die Verarbeitung dort gilt die Datenschutzerklärung des jeweiligen Anbieters.\n7. Ihre Rechte # Sie haben nach der DSGVO jederzeit das Recht auf:\nAuskunft über Ihre bei mir gespeicherten Daten (Art. 15 DSGVO) Berichtigung unrichtiger Daten (Art. 16 DSGVO) Löschung (Art. 17 DSGVO) Einschränkung der Verarbeitung (Art. 18 DSGVO) Datenübertragbarkeit (Art. 20 DSGVO) Widerspruch gegen Verarbeitungen auf Grundlage von Art. 6 Abs. 1 lit. f DSGVO (Art. 21 DSGVO) Eine formlose E-Mail an die oben genannte Adresse genügt.\nAußerdem haben Sie das Recht, sich bei einer Datenschutz-Aufsichtsbehörde zu beschweren (Art. 77 DSGVO). Für mich zuständig ist die Landesbeauftragte für Datenschutz und Informationsfreiheit Nordrhein-Westfalen (ldi.nrw.de).\nStand: November 2026\n","date":"1. Januar 0001","permalink":"https://www.gesellix.net/de/datenschutz/","section":"gesellix.net","summary":"","title":"Datenschutzerklärung"},{"content":"Angaben gemäß § 5 DDG:\nTobias Gesellchen Softwareentwickler Ürziger Straße 8 53175 Bonn Deutschland\nE-Mail: tobias@gesellix.de\nVerantwortlich für den Inhalt nach § 18 Abs. 2 MStV # Tobias Gesellchen Ürziger Straße 8 53175 Bonn Deutschland\nHaftungsausschluss # Haftung für Inhalte # Die Inhalte dieser Webseite wurden mit größter Sorgfalt erstellt. Für die Richtigkeit, Vollständigkeit und Aktualität der Inhalte übernehme ich jedoch keine Gewähr.\nHaftung für Links # Diese Webseite enthält Links zu externen Webseiten Dritter, auf deren Inhalte ich keinen Einfluss habe. Für diese fremden Inhalte übernehme ich keine Gewähr. Für die Inhalte der verlinkten Seiten ist stets der jeweilige Anbieter oder Betreiber verantwortlich.\nVerbraucherstreitbeilegung # Ich bin nicht verpflichtet und nicht bereit, an Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle teilzunehmen.\n","date":"1. Januar 0001","permalink":"https://www.gesellix.net/de/impressum/","section":"gesellix.net","summary":"","title":"Impressum"},{"content":" Hallo, du hast das Blog von Tobias gefunden, einem Softwareentwickler aus Deutschland!\nIch beschäftige mich gern mit Technik, entwickle Open-Source-Projekte und spiele leidenschaftlich gern Lego mit meinen Kindern. Ein ehemaliger Kollege hat mal gesagt: „Ich werde für meinen Spieltrieb bezahlt.“ Genau das mache ich beruflich :)\nSoftwareentwicklung ist für mich mit der Zeit weniger technisch geworden. Aktuell arbeite ich als Engineering Manager, es geht also mehr um Menschen und weniger um Software. Private Projekte und Freelancing helfen mir, nah am Code zu bleiben.\nDie meisten Beiträge in diesem Blog sind Gedanken zu Projekten, an denen ich privat in meiner Freizeit oder beruflich arbeite. Ab und zu geht es auch um meine anderen Hobbys, zum Beispiel Fahrräder oder Filme. Die meisten Beiträge schreibe ich auf Englisch.\nAm aktivsten bin ich auf GitHub und X/Twitter. Melde dich gern dort!\n","date":"1. Januar 0001","permalink":"https://www.gesellix.net/de/ueber-mich/","section":"gesellix.net","summary":"","title":"Über mich"}]