{"id":15069079,"url":"https://github.com/vmj/http-server","last_synced_at":"2025-10-25T14:15:10.590Z","repository":{"id":139272410,"uuid":"86160268","full_name":"vmj/http-server","owner":"vmj","description":"A Java HTTP server in 35MB Docker image","archived":false,"fork":false,"pushed_at":"2018-05-05T19:58:02.000Z","size":96,"stargazers_count":17,"open_issues_count":0,"forks_count":6,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-10T18:03:22.115Z","etag":null,"topics":["alpine","alpine-linux","docker","http","http-server","java","java-10","java-11","java-9","java-module","jdk10","jdk11","jdk9","jigsaw","jlink"],"latest_commit_sha":null,"homepage":"","language":"Makefile","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vmj.png","metadata":{"files":{"readme":"README.adoc","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-03-25T13:44:08.000Z","updated_at":"2022-08-27T15:46:01.000Z","dependencies_parsed_at":null,"dependency_job_id":"26cbd2fd-87bd-4cb3-9a27-c8c1f203fcd3","html_url":"https://github.com/vmj/http-server","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/vmj/http-server","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vmj%2Fhttp-server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vmj%2Fhttp-server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vmj%2Fhttp-server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vmj%2Fhttp-server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vmj","download_url":"https://codeload.github.com/vmj/http-server/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vmj%2Fhttp-server/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280965555,"owners_count":26421660,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-25T02:00:06.499Z","response_time":81,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["alpine","alpine-linux","docker","http","http-server","java","java-10","java-11","java-9","java-module","jdk10","jdk11","jdk9","jigsaw","jlink"],"created_at":"2024-09-25T01:40:22.102Z","updated_at":"2025-10-25T14:15:10.563Z","avatar_url":"https://github.com/vmj.png","language":"Makefile","readme":"== Smaller Java apps in Docker\n\nimage:https://travis-ci.org/vmj/http-server.svg?branch=master[\"Build Status\", link=\"https://travis-ci.org/vmj/http-server/branches\"]\n\nThis project showcases two features of the JDK:\n\n * ability to create custom runtime for your application\n * native support for Alpine Linux\n\nThose both boil down to more efficient container usage.\nSpacewise.\n\nCreating a custom runtime for your app means\nthat you can drop all the useless modules from your packaging.\nLike in this example,\nwhy should a server application ship with all the user interface toolkits\nthat Java ships with (AWT, Swing, and JavaFX).\n\nBeing able to put that custom runtime natively on Alpine Linux gives you\nreally slim end result.\nPeople are routinely using Alpine Linux for small containers,\nand community has maintained a patched JRE for Java people.\nThat was fine for a long time,\nbut now we no longer have to use an unofficial JRE.\n\n=== Results\n\nThe end result will be that you've got a pretty small, but functional,\ndocker image for a Java app:\n\n  $ docker images\n  REPOSITORY                     TAG           IMAGE ID      CREATED         SIZE\n  vmj0/http-server-linux-java11  1.0-SNAPSHOT  ef252ec83afd  11 minutes ago  101MB\n  vmj0/http-server-alpine-java11 1.0-SNAPSHOT  7854466a32f3  12 minutes ago  35.2MB\n  alpine                         3.6           77144d8c6bdc  3 months ago    3.97MB\n  vbatts/slackware               13.37         777ef0cd51ed  6 months ago    69.8MB\n\nAs can be seen from above, the `vmj0/http-server-alpine-java11` is less than\nhalf of the `vmj0/http-server-linux-java11`.\nThe latter is based on a typical linux distro, `vbatts/slackware` in this case.\n\nBelow, I will show you the commands you can use to manually come up with above result.\nIt will be a lot of typing, and in reality you would use a build tool to automate it.\n\n=== Starting point\n\nYou will need a recent JDK, version 9 or above.\nI'm using JDK 9 even though it has been obsoleted by JDK 10.\n\n  $ java -version\n  openjdk version \"9.0.4\"\n  OpenJDK Runtime Environment (build 9.0.4+11)\n  OpenJDK 64-Bit Server VM (build 9.0.4+11, mixed mode)\n\nAs you probably know, JDK was modularized.\nLet's see how many modules this JDK alone has:\n\n  $ java --list-modules |wc -l\n        73\n\nIn order to follow these steps, you will also need Docker and git installed.\n\n=== Clone this repository\n\n  $ git clone https://github.com/vmj/http-server.git\n  $ cd http-server\n\n=== Compile the Java source code to class files\n\nThis project has just two Java files: the module info and the main class.\nThe compilation is nothing new:\n\n  $ rm -rf build/classes/main\n  $ mkdir -p build/classes/main\n  $ javac -d build/classes/main \\\n      src/main/java/module-info.java \\\n      src/main/java/fi/linuxbox/http/Main.java\n\n=== Run the class files as a module\n\nSince we have the module-info in there,\nwe can put the class files on the module path as opposed to classpath,\nand run the module:\n\n  $ java --module-path build/classes/main \\\n      -m http.server/fi.linuxbox.http.Main\n\nAfter testing (see below),\nuse `CTRL-C` or similar to interrupt the application.\n\n=== Test\n\nWhile the server is running, hit it with requests. E.g.:\n\n  $ curl http://localhost:9000/\n  Hello World\n\nThis little server also supports the `HEAD` method:\n\n  $ curl --head http://localhost:9000/\n  HTTP/1.1 200 OK\n  Date: Sun, 18 Jun 2017 15:37:11 GMT\n  Content-type: text/plain; charset=utf-8\n\nAnd `OPTIONS` (note the `Allow` header in the response):\n\n  $ curl -v -X OPTIONS http://localhost:9000/\n  *   Trying ::1...\n  * TCP_NODELAY set\n  * Connected to localhost (::1) port 9000 (#0)\n  \u003e OPTIONS / HTTP/1.1\n  \u003e Host: localhost:9000\n  \u003e User-Agent: curl/7.54.0\n  \u003e Accept: */*\n  \u003e\n  \u003c HTTP/1.1 200 OK\n  \u003c Date: Sun, 18 Jun 2017 15:37:21 GMT\n  \u003c Allow: GET, OPTIONS\n  \u003c Content-type: text/plain; charset=utf-8\n  \u003c Content-length: 0\n  \u003c\n  * Connection #0 to host localhost left intact\n\n=== Package the class files as a modular JAR\n\nThe class files by themselves are a bit inconvenient to ship,\nso let's build a modular JAR:\n\n  $ rm -rf build/jmods\n  $ mkdir -p build/jmods\n  $ jar --create --file build/jmods/http-server-1.0-SNAPSHOT.jar \\\n      --main-class fi.linuxbox.http.Main \\\n      -C build/classes/main .\n\nAs you can see, there's nothing new here:\nour `javac` and `jar` invocations look exactly like they used to.\n\n=== Run the modular JAR\n\nNow running the application looks like this:\n\n  $ java --module-path build/jmods -m http.server\n\nWe're just putting the JAR, instead of the class files, on the module path.\nAlso, there's no need to specify the main class anymore.\n\nTo verify that it works, you can go through the \u003c\u003cTest\u003e\u003e section again.\n\n=== Create a custom runtime\n\nThe modular JAR is a fine distribution form for libraries,\nbut for applications we can do a bit better.\n\nOne of the coolest features that came with JDK 9, IMHO, is the `jlink` command.\nIt allows you to build a custom runtime just for your app.\n\nFor convenience,\nlet's define an environment variable that points to the JDK modules.\nYou will need to adjust the directory here,\nsince this one is specific to my setup.\nYou can find the `jmods` directory from within your Java installation.\n\n  $ export TARGET_JMODS=/Users/vmj/.sdkman/candidates/java/9.0.4-openjdk/jmods\n\nNOTE: While you can use JDK 9 to compile and package (as a JAR) your code,\nyou can't use JDK 9 `jlink` if you are building a custom runtime for Java 10 or 11.\nThe major version number of `jlink` has to match the major version number of the JDK modules.\nWe will address this further down.\n\nNow, here's the basic usage of `jlink`:\n\n  $ jlink --module-path build/jmods:$TARGET_JMODS \\\n      --add-modules http.server \\\n      --output build/jre/native\n\nIt will analyze the `http.server` module dependencies transitively,\nand spit out a small runtime:\n\n  $ du -csh build/jre/native\n   35M    build/jre/native\n   35M    total\n  $ build/jre/native/bin/java --list-modules\n  http.server\n  java.base@9.0.4\n  java.logging@9.0.4\n  jdk.httpserver@9.0.4\n\nSo now you've got a 35MB directory that includes your app, its dependencies,\nand a `java` executable.\nYou're down from 74 modules (73 for the JDK and 1 for your app)\nto just 4 modules.\nNice :)\n\n=== Optimize the custom runtime\n\nTurns out that you can shrink the custom runtime even more.\nLet's build it again with some more flags:\n\n  $ rm -rf build/jre/native\n  $ jlink --module-path build/jmods:$TARGET_JMODS \\\n        --strip-debug --vm server --compress 2 \\\n        --class-for-name \\\n        --exclude-jmod-section=headers --exclude-jmod-section=man \\\n        --dedup-legal-notices=error-if-not-same-content \\\n        --add-modules http.server \\\n        --output build/jre/native\n  $ du -csh build/jre/native\n   21M    build/jre/native\n   21M    total\n\nThat's more than 40% off of an already small base :)\n\n=== Run the module in the custom runtime\n\nJust to check that things are still working,\nyou can run the app using the custom runtime like this:\n\n  $ ./build/jre/native/bin/java -m http.server\n\nAnd the \u003c\u003cTest\u003e\u003e section should look familiar by now.\n\nNow you could zip that directory and send it to everyone who's using the\nsame platform as you are.  (That's why I chose the name `native`.)\n\n=== Containerize the custom runtime\n\nIn order to be platform agnostic (this is Java app after all),\nwe can Dockerize the custom runtime.\n\nNOTE: the custom runtime needs to be cross-compiled for Linux,\nbecause that's what's running in the container.\nDon't worry, JDK folks have made it child's play :)\n\nMost of the Linux distributions use the GNU C library known as glibc.\nAlpine Linux, in order to shrink the size of the distribution,\nis based on http://www.musl-libc.org/[musl C library].\nHence, the \"normal\" Linux JDK builds are not compatible with that\nbecause they are linked against glibc.\n\nLuckily, http://openjdk.java.net/projects/portola/[Project Portola]\nported the JDK to Alpine Linux,\nand their effort was already included in the JDK 9 EA build 171,\nreleased at the beginning of June 2017.\nAs of this writing,\nthere doesn't seem to be JDK 9 or 10 builds available anymore,\nbut the latest JDK 11 for Alpine Linux is\navailable at the http://jdk.java.net/11/[Early Access] page.\n\n==== Download and extract the target JDK(s)\n\nSo, in order to cross-compile, you will need to download the target JDK.\nJRE is not enough.\nAlso, you will need a matching native JDK for the linking to work.\n\nYou don't have to install these JDKs, just extract them somewhere.\n\nHead on to http://jdk.java.net/11/ and grab your native JDK\nand the Alpine Linux JDK.\nIf you want to compare to a Linux distribution that is based on glibc,\ngrab the Linux JDK, too. (Unless that happens to be your native platform.)\n\nThen extract the JDK(s) somewhere.\nFor example, I've got the Alpine JDK in `/Users/vmj/jdks/x64-musl/`\nand Linux JDK in `/Users/vmj/jdks/x64-linux/`.\n\n  $ cd ~/jdks/x64-musl\n  $ tar xzf openjdk-11-ea+11_linux-x64-musl_bin.tar.gz\n  $ cd ../x64-linux\n  $ tar xzf openjdk-11-ea+12_linux-x64_bin.tar.gz\n  $ cd ../x64-osx\n  $ tar xzf openjdk-11-ea+12_osx-x64_bin.tar.gz\n\n\n==== Cross-compile the custom runtime(s)\n\nPoint your `TARGET_JMODS` env var to the target JDK.  Here I'm pointing it to Alpine Linux modules:\n\n  $ export TARGET_JMODS=/Users/vmj/jdks/x64-musl/jdk-11/jmods\n\nAnd for convenience, point `JLINK` env var to the matching native `jlink`.\nSince I have JDK 9 as my regular JDK, I point the `JLINK` to the JDK 11 version I extracted above:\n\n  $ export JLINK=/Users/vmj/jdks/x64-osx/jdk-11.jdk/Contents/Home/bin/jlink\n\nNow go back to the project directory and\nbuild the custom runtime for Alpine:\n\n  $ $JLINK --module-path build/jmods:$TARGET_JMODS \\\n        --strip-debug --vm server --compress 2 \\\n        --class-for-name \\\n        --exclude-jmod-section=headers --exclude-jmod-section=man \\\n        --dedup-legal-notices=error-if-not-same-content \\\n        --add-modules http.server \\\n        --output build/jre/alpine\n\nNote that we're now pointing the module path to the target JDK\ninstead of that of the build host.\n`jlink`, which we launch from the native JDK,\nwill notice that we're cross-compiling,\nand it will spit out a different result.\n\nWe're also changing the output directory,\njust so we can have multiple custom runtimes.\n\nYou can optionally run `jlink` again\nwith `TARGET_JMODS` pointing to the Linux JDK\nand with the option `--output build/jre/linux`.\nThat will give you a glibc based runtime for comparison.\n\n  $ export TARGET_JMODS=/Users/vmj/jdks/x64-linux/jdk-11/jmods\n  $ $JLINK --module-path build/jmods:$TARGET_JMODS \\\n        --strip-debug --vm server --compress 2 \\\n        --class-for-name \\\n        --exclude-jmod-section=headers --exclude-jmod-section=man \\\n        --dedup-legal-notices=error-if-not-same-content \\\n        --add-modules http.server \\\n        --output build/jre/linux\n\n==== Prepare the Dockerfile(s)\n\nLet's create some simplistic Dockerfiles for our images:\n\n  $ rm -rf build/dockerfile\n  $ mkdir -p build/dockerfile\n  $ sed -e 's BASE_IMAGE alpine:3.6 ' Dockerfile.in \u003ebuild/dockerfile/alpine\n  $ sed -e 's BASE_IMAGE vbatts/slackware:13.37 ' Dockerfile.in \u003ebuild/dockerfile/linux\n\nThe second `sed` invocation is optional.\nIn it, you could also use `debian:stretch-slim` or\npretty much any glibc based Linux distribution.\n\n==== Build the Docker image(s)\n\nNow we can do the docker dance.\nFirst create a docker build context:\n\n  $ rm -rf build/docker\n  $ mkdir -p build/docker\n\nThen copy the custom runtime and the `Dockerfile` to the build context:\n\n  $ cp -a build/jre/alpine build/docker/jre\n  $ cp build/dockerfile/alpine build/docker/Dockerfile\n\nNow you can upload the build context to the docker daemon and build the image:\n\n  $ (cd build/docker \u0026\u0026 docker build --tag vmj0/http-server-alpine-java11:1.0-SNAPSHOT .)\n\nAnd, you can do the same dance for Linux,\njust replacing alpine with linux in three places:\n\n  $ rm -rf build/docker\n  $ mkdir -p build/docker\n  $ cp -a build/jre/linux build/docker/jre\n  $ cp build/dockerfile/linux build/docker/Dockerfile\n  $ (cd build/docker \u0026\u0026 docker build --tag vmj0/http-server-linux-java11:1.0-SNAPSHOT .)\n\n=== Run the container image\n\nRunning the container is old news:\n\n  docker run --rm -it -p9000:9000 vmj0/http-server-alpine-java11:1.0-SNAPSHOT\n\nAnd checking that it works is... yes, in the \u003c\u003cTest\u003e\u003e section.\n\n=== Some addition options\n\nAbove is all very tedious.\nBut now that the process is known, you can use whatever build tool that you're comfortable with.\nHere are a couple of examples.\n\n==== Multistage Dockerfile\n\nYou can build the Alpine Docker image in one command using the `Dockerfile.multistage`,\nwhich was contributed by https://github.com/StevenACoffman[@StevenACoffman] (thanks!).\nFor example:\n\n  docker build -f Dockerfile.multistage --tag vmj0/http-server-multistage:1.0-SNAPSHOT .\n\n==== Makefile\n\nYou probably noticed the `Makefile`.\nIt's optional, since I've shown you above how to do things,\nbut the `Makefile` contains all the above commands.\n\nIf you've got GNU make, try invoking `make help`.\nOr do the following (adjusting variables, naturally) and read the help later:\n\n  $ unset TARGET_JMODS\n  $ export NATIVE_JMODS=/Users/vmj/jdks/x64-osx/jdk-11.jdk/Contents/Home/jmods\n  $ export ALPINE_JMODS=/Users/vmj/jdks/x64-musl/jdk-11/jmods\n  $ export LINUX_JMODS=/Users/vmj/jdks/x64-linux/jdk-11/jmods\n  $ for target in native alpine linux ; do make jre TARGET=$target ; done\n  $ export DOCKER_NAME=vmj0\n  $ for target in alpine linux ; do make dockerImage TARGET=$target ; done\n\nHave fun!\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvmj%2Fhttp-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvmj%2Fhttp-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvmj%2Fhttp-server/lists"}