{"id":19012956,"url":"https://github.com/ntk148v/koker","last_synced_at":"2025-04-22T23:49:55.947Z","repository":{"id":65193803,"uuid":"512713330","full_name":"ntk148v/koker","owner":"ntk148v","description":"Docker-like tool from scratch","archived":false,"fork":false,"pushed_at":"2025-02-06T04:27:48.000Z","size":3512,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-22T23:49:48.078Z","etag":null,"topics":["bridge","cgroups","cgroups-v2","container","containers","docker","dyi","koker","linux","namespaces"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ntk148v.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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,"zenodo":null},"funding":{"github":"ntk148v","patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"lfx_crowdfunding":null,"custom":null}},"created_at":"2022-07-11T10:34:35.000Z","updated_at":"2025-02-06T04:27:40.000Z","dependencies_parsed_at":"2025-02-05T10:24:51.919Z","dependency_job_id":"e312dd1f-7dc4-4fe8-a023-d58dc6afc91d","html_url":"https://github.com/ntk148v/koker","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ntk148v%2Fkoker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ntk148v%2Fkoker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ntk148v%2Fkoker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ntk148v%2Fkoker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ntk148v","download_url":"https://codeload.github.com/ntk148v/koker/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250343909,"owners_count":21415037,"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","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":["bridge","cgroups","cgroups-v2","container","containers","docker","dyi","koker","linux","namespaces"],"created_at":"2024-11-08T19:21:00.524Z","updated_at":"2025-04-22T23:49:55.934Z","avatar_url":"https://github.com/ntk148v.png","language":"Go","readme":"\u003ch1 align=\"center\"\u003eKoker\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003eBuilding a \u003cb\u003eKoker\u003c/b\u003e - Kien's mini Docker.\u003c/p\u003e\n\u003cp align=\"center\"\u003e\u003ci\u003eWhat I cannot create, I do not understand — Richard Feynman\u003c/i\u003e\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://github.com/ntk148v/koker/blob/master/LICENSE\"\u003e\n        \u003cimg alt=\"GitHub license\" src=\"https://img.shields.io/github/license/ntk148v/koker?style=for-the-badge\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://github.com/ntk148v/koker/stargazers\"\u003e\u003cimg src=\"https://img.shields.io/github/stars/ntk148v/koker?colorA=192330\u0026colorB=719cd6\u0026style=for-the-badge\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/ntk148v/koker/issues\"\u003e\u003cimg src=\"https://img.shields.io/github/issues/ntk148v/koker?colorA=192330\u0026colorB=dbc074\u0026style=for-the-badge\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/ntk148v/koker/contributors\"\u003e\u003cimg src=\"https://img.shields.io/github/contributors/ntk148v/koker?colorA=192330\u0026colorB=81b29a\u0026style=for-the-badge\"\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/ntk148v/koker/network/members\"\u003e\u003cimg src=\"https://img.shields.io/github/forks/ntk148v/koker?colorA=192330\u0026colorB=9d79d6\u0026style=for-the-badge\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n- [1. Introduction](#1-introduction)\n- [2. Getting started](#2-getting-started)\n- [3. Examples](#3-examples)\n- [4. Contributing](#4-contributing)\n\n## 1. Introduction\n\n- What is **Koker**?\n  - Koker is a tiny educational-purpose Docker-like tool, written in Golang.\n  - Unlike Docker, Koker just uses a set of Linux's operating system primitives that provide the illusion of a container. It uses neither [containerd](https://containerd.io/) nor [runc](https://github.com/opencontainers/runc).\n- Why **Koker**?\n  - Have you ever wondered how Docker containers are constructed?\n  - Koker provides an understanding of how extactly containers work at the Linux system call level by using logging (every steps!).\n    - Control Groups for resource restriction (CPU, Memory, Swap, PIDs). _All CGroups modes (Legacy - v1, Hybrid - v1 \u0026 v2, Unified - v2) are handled_.\n    - Namespace for global system resources isolation (Mount, UTS, Network, IPS, PID).\n    - Union File System for branches to be overlaid in a single coherent file system. (OverlayFS)\n    - Container networking using bridge and iptables.\n- **Koker** is highly inspired by:\n  - [Bocker](https://github.com/p8952/bocker).\n  - [Containers-the-hard-way](https://github.com/shuveb/containers-the-hard-way)\n  - [Vessel](https://github.com/0xc0d/vessel)\n- Should I use **Koker** in production?\n  - Nope, Koker isn't a production ready tool!\n- Can **Koker** perform every Docker tasks?\n  - Nope, ofc, Koker doesn't aim to recreate every Docker's tasks. There are just some simple tasks for educational-purpose.\n- If you are looking for a tool similar to Docker in terms of functionality but do not require a daemon to operate, Podman is a suitable choice for you.\n\n## 2. Getting started\n\n- Install:\n\n```shell\n$ go get -u github.com/ntk148v/koker\n# Or you can build yourself\n$ git clone https://github.com/ntk148v/koker.git koker\n$ cd koker\n$ make build\n$ sudo /tmp/koker --help\n```\n\n- You can also download binary file in [releases](https://github.com/ntk148v/koker/releases).\n\n- Usage:\n  - Note that you must have root permission to execute koker.\n\n```shell\n$ sudo koker --help\nNAME:\n   koker - Kien's mini Docker\n\nUSAGE:\n   koker [global options] command [command options] [arguments...]\n\nVERSION:\n   v0.0.1\n\nAUTHOR:\n   Kien Nguyen-Tuan \u003ckiennt2609@gmail.com\u003e\n\nCOMMANDS:\n   container, c  Manage container\n   image, i      Manage images\n   help, h       Shows a list of commands or help for one command\n\nGLOBAL OPTIONS:\n   --debug, -D    Set log level to debug. You will see step-by-step what were executed (default: false)\n   --help, -h     show help (default: false)\n   --quiet, -q    Disable logging altogether (quiet mode) (default: false)\n   --version, -v  print the version (default: false)\n```\n\n```shell\n$ sudo koker container --help\nNAME:\n   koker container - Manage container\n\nUSAGE:\n   koker container command [command options] [arguments...]\n\nCOMMANDS:\n     run      Run a command in a new container\n     child\n     rm       Remove a container (WIP)\n     ls       List running containers\n     exec     Run a command inside a running container\n     help, h  Shows a list of commands or help for one command\n\nOPTIONS:\n   --help, -h  show help (default: false)\n```\n\n```shell\n$ sudo koker image --help\nNAME:\n   koker image - Manage images\n\nUSAGE:\n   koker image command [command options] [arguments...]\n\nCOMMANDS:\n     ls       List all available images\n     pull     Pull an image or a repository from a registry (using image's name)\n     rm       Remove a image (using image's name)\n     help, h  Shows a list of commands or help for one command\n\nOPTIONS:\n   --help, -h  show help (default: false)\n```\n\n## 3. Examples\n\n- Start container and execute command.\n\n```shell\n$ sudo koker -D container run --hostname test --mem 1024 alpine sh # Enable debugging\n\n11:08AM INF Load image repository from file repository=/var/lib/koker/images/repositories.json\n11:08AM DBG Load image repository\n11:08AM DBG Check default bridge is up or not bridge=koker0\n11:08AM INF Setup network for container container=ccjuq1p3l1hn8clpgib0\n11:08AM DBG Setup virtual ethernet peer=veth1_ccjumf9 virt=veth0_ccjumf9\n11:08AM DBG Set the master of the link device link=veth0_ccjumf9 master=koker0\n11:08AM DBG Mount new network namespace netns=/var/lib/koker/netns/ccjuq1p3l1hn8clpgib0\n11:08AM DBG Call syscall unshare CLONE_NEWNET netns=/var/lib/koker/netns/ccjuq1p3l1hn8clpgib0\n11:08AM DBG Mount new network namespace netns=/var/lib/koker/netns/ccjuq1p3l1hn8clpgib0\n11:08AM DBG Mount target source=/proc/self/ns/net target=/var/lib/koker/netns/ccjuq1p3l1hn8clpgib0\n11:08AM DBG Set network namespace netns=/var/lib/koker/netns/ccjuq1p3l1hn8clpgib0\n11:08AM DBG Put link device into a new network namespace link=veth1_ccjumf9 netns=/var/lib/koker/netns/ccjuq1p3l1hn8clpgib0\n11:08AM DBG Set network namespace by file netns=/var/lib/koker/netns/ccjuq1p3l1hn8clpgib0\n11:08AM DBG Change the name of the link device newname=eth0 oldname=veth1_ccjumf9\n11:08AM DBG Add IP address to the ip device ip=172.69.216.38/16 link=eth0\n11:08AM DBG Enable the link device link=eth0\n11:08AM DBG Set gateway for the link device gateway=172.69.0.1 link=eth0\n11:08AM DBG Enable the link device link=lo\n11:08AM INF Construct new Image instance image=alpine\n11:08AM INF Image exists, reuse image=index.docker.io/library/alpine:latest\n11:08AM INF Mount filesystem for container from an image container=ccjuq1p3l1hn8clpgib0 image=index.docker.io/library/alpine:latest\n11:08AM DBG Mount target source=none target=/var/lib/koker/containers/ccjuq1p3l1hn8clpgib0/mnt\n11:08AM DBG Copy container config from image config container=ccjuq1p3l1hn8clpgib0 image=library/alpine\n11:08AM INF Load image repository from file repository=/var/lib/koker/images/repositories.json\n11:08AM DBG Load image repository\n11:08AM DBG Load container config from file container=ccjuq1p3l1hn8clpgib0\n11:08AM INF Set hostname container=ccjuq1p3l1hn8clpgib0\n11:08AM INF Set container's limit using cgroup container=ccjuq1p3l1hn8clpgib0\n11:08AM DBG Set container's memory limit container=ccjuq1p3l1hn8clpgib0\n11:08AM DBG Set container's pids limit container=ccjuq1p3l1hn8clpgib0\n11:08AM DBG Set container's cpus limit container=ccjuq1p3l1hn8clpgib0\n11:08AM INF Copy nameserver config container=ccjuq1p3l1hn8clpgib0\n11:08AM INF Execute command container=ccjuq1p3l1hn8clpgib0\n11:08AM DBG Set network namespace container=ccjuq1p3l1hn8clpgib0\n11:08AM DBG Set network namespace by file netns=/var/lib/koker/netns/ccjuq1p3l1hn8clpgib0\n11:08AM DBG Mount target source=tmpfs target=dev\n11:08AM DBG Mount target source=proc target=proc\n11:08AM DBG Mount target source=sysfs target=sys\n11:08AM DBG Mount target source=tmpfs target=tmp\n11:08AM DBG Execute command command=sh container=ccjuq1p3l1hn8clpgib0\n/ #\n# Hit \u003cCtrl+c\u003e\n11:09AM DBG Unmount target source=tmpfs target=dev\n11:09AM DBG Unmount target source=proc target=proc\n11:09AM DBG Unmount target source=sysfs target=sys\n11:09AM DBG Unmount target source=tmpfs target=tmp\n11:09AM INF Save image repository to file repository=/var/lib/koker/images/repositories.json\n11:09AM DBG Unmount target source=none target=/var/lib/koker/containers/ccjuq1p3l1hn8clpgib0/mnt\n11:09AM DBG Unmount target source=/proc/self/ns/net target=/var/lib/koker/netns/ccjuq1p3l1hn8clpgib0\n11:09AM INF Delete container container=ccjuq1p3l1hn8clpgib0\n11:09AM DBG Remove container's directory container=ccjuq1p3l1hn8clpgib0\n11:09AM DBG Remove container's network namespace container=ccjuq1p3l1hn8clpgib0\n11:09AM DBG Remove container cgroups container=ccjuq1p3l1hn8clpgib0\n11:09AM INF Save image repository to file repository=/var/lib/koker/images/repositories.json\n```\n\n- Pull and list image(s).\n\n```shell\n$ sudo koker -D image pull alpine\n$ sudo koker -D image ls\n11:13AM INF Load image repository from file repository=/var/lib/koker/images/repositories.json\n11:13AM DBG Load image repository\n\nREPOSITORY              TAG             IMAGE ID\n\njwilder/whoami          latest          4a4c1589a078\n\nlibrary/alpine          3.16.2          0261ca8a4a79\n\nlibrary/alpine          edge            9a2e669787f4\n\nlibrary/alpine          latest          0261ca8a4a79\n\n11:13AM INF Save image repository to file repository=/var/lib/koker/images/repositories.json\n```\n\n- List all available containers (run a container then run the above command in the another session).\n\n```shell\n$ koker -D container ls\n11:11AM INF Load image repository from file repository=/var/lib/koker/images/repositories.json\n11:11AM DBG Load image repository\n11:11AM DBG Load container config from file container=ccjuo013l1hkmh7sk540\n\nCONTAINER ID            IMAGE                   COMMAND\n\nccjuq1p3l1hn8clpgib0    0261ca8a4a79            sh\n\n11:11AM INF Save image repository to file repository=/var/lib/koker/images/repositories.json\n```\n\n- Run a command inside a running container.\n\n```shell\n$ sudo koker -D container exec ccjuq1p3l1hn8clpgib0 sh\n11:17AM INF Load image repository from file repository=/var/lib/koker/images/repositories.json\n11:17AM DBG Load image repository\n11:17AM DBG Load container config from file container=ccjuq1p3l1hn8clpgib0\n11:17AM INF Execute command container=ccjuq1p3l1hn8clpgib0\n11:17AM DBG Execute command command=sh container=ccjuq1p3l1hn8clpgib0\n/ #\n```\n\n- Run container with limited resource.\n  - Run golang-memtest container to allocate 20MiB in 10MiB container.\n\n```shell\n$ sudo koker container run --mem 10 fabianlee/golang-memtest:1.0.0 20\n9:20AM INF Load image repository from file repository=/var/lib/koker/images/repositories.json\n9:20AM INF Setup network for container container=cdmr2vmfvq0sn7gs7r80\n9:20AM INF Construct new Image instance image=fabianlee/golang-memtest:1.0.0\n9:20AM INF Image exists, reuse image=index.docker.io/fabianlee/golang-memtest:1.0.0\n9:20AM INF Mount filesystem for container from an image container=cdmr2vmfvq0sn7gs7r80 image=index.docker.io/fabianlee/golang-memtest:1.0.0\n9:20AM INF Load image repository from file repository=/var/lib/koker/images/repositories.json\n9:20AM INF Set hostname container=cdmr2vmfvq0sn7gs7r80\n9:20AM INF Set container's limit using cgroup container=cdmr2vmfvq0sn7gs7r80\n9:20AM INF Copy nameserver config container=cdmr2vmfvq0sn7gs7r80\n9:20AM INF Execute command container=cdmr2vmfvq0sn7gs7r80\nAlloc = 0 MiB   TotalAlloc = 0 MiB      Sys = 68 MiB    NumGC = 0\nAsked to allocate 20Mb\n\nAlloc = 1 MiB   TotalAlloc = 1 MiB      Sys = 68 MiB    NumGC = 0\nAlloc = 2 MiB   TotalAlloc = 2 MiB      Sys = 68 MiB    NumGC = 0\nAlloc = 3 MiB   TotalAlloc = 3 MiB      Sys = 68 MiB    NumGC = 0\nAlloc = 4 MiB   TotalAlloc = 4 MiB      Sys = 68 MiB    NumGC = 1\nAlloc = 5 MiB   TotalAlloc = 5 MiB      Sys = 68 MiB    NumGC = 1\nAlloc = 6 MiB   TotalAlloc = 6 MiB      Sys = 68 MiB    NumGC = 1\nAlloc = 7 MiB   TotalAlloc = 7 MiB      Sys = 68 MiB    NumGC = 1\nAlloc = 8 MiB   TotalAlloc = 8 MiB      Sys = 68 MiB    NumGC = 2\n9:20AM ERR Something went wrong error=\"error running child command: signal: killed\"\n9:20AM INF Save image repository to file repository=/var/lib/koker/images/repositories.json\n9:20AM INF Delete container container=cdmr2vmfvq0sn7gs7r80\n9:20AM INF Save image repository to file repository=/var/lib/koker/images/repositories.json\n```\n\n- Connect to outside the world from the container:\n\n```shell\n$ sudo koker -D container run alpine sh\n11:25AM INF Load image repository from file repository=/var/lib/koker/images/repositories.json\n11:25AM DBG Load image repository\n11:25AM DBG Check default bridge is up or not bridge=koker0\n11:25AM DBG Append POSTROUTING rule outgoing=koker0 source=172.69.0.0/16\n11:25AM INF Setup network for container container=cui3je94h280e1c7nae0\n11:25AM DBG Setup virtual ethernet peer=veth1_cui3je9 virt=veth0_cui3je9\n11:25AM DBG Set the master of the link device link=veth0_cui3je9 master=koker0\n11:25AM DBG Mount new network namespace netns=/var/lib/koker/netns/cui3je94h280e1c7nae0\n11:25AM DBG Call syscall unshare CLONE_NEWNET netns=/var/lib/koker/netns/cui3je94h280e1c7nae0\n11:25AM DBG Mount new network namespace netns=/var/lib/koker/netns/cui3je94h280e1c7nae0\n11:25AM DBG Mount target source=/proc/self/ns/net target=/var/lib/koker/netns/cui3je94h280e1c7nae0\n11:25AM DBG Set network namespace netns=/var/lib/koker/netns/cui3je94h280e1c7nae0\n11:25AM DBG Put link device into a new network namespace link=veth1_cui3je9 netns=/var/lib/koker/netns/cui3je94h280e1c7nae0\n11:25AM DBG Set network namespace by file netns=/var/lib/koker/netns/cui3je94h280e1c7nae0\n11:25AM DBG Change the name of the link device newname=eth0 oldname=veth1_cui3je9\n11:25AM DBG Add IP address to the ip device ip=172.69.98.139/16 link=eth0\n11:25AM DBG Enable the link device link=eth0\n11:25AM DBG Set gateway for the link device gateway=172.69.0.1 link=eth0\n11:25AM DBG Enable the link device link=lo\n11:25AM INF Construct new Image instance image=alpine\n11:25AM INF Image exists, reuse image=index.docker.io/library/alpine:latest\n11:25AM INF Mount filesystem for container from an image container=cui3je94h280e1c7nae0 image=index.docker.io/library/alpine:latest\n11:25AM DBG Mount target source=none target=/var/lib/koker/containers/cui3je94h280e1c7nae0/mnt\n11:25AM DBG Copy container config from image config container=cui3je94h280e1c7nae0 image=library/alpine\n11:25AM INF Load image repository from file repository=/var/lib/koker/images/repositories.json\n11:25AM DBG Load image repository\n11:25AM DBG Load container config from file container=cui3je94h280e1c7nae0\n11:25AM INF Set hostname container=cui3je94h280e1c7nae0\n11:25AM INF Set container's limit using cgroup container=cui3je94h280e1c7nae0\n11:25AM DBG Set container's memory limit container=cui3je94h280e1c7nae0\n11:25AM DBG Set container's pids limit container=cui3je94h280e1c7nae0\n11:25AM DBG Set container's cpus limit container=cui3je94h280e1c7nae0\n11:25AM INF Copy nameserver config container=cui3je94h280e1c7nae0\n11:25AM INF Execute command container=cui3je94h280e1c7nae0\n11:25AM DBG Set network namespace container=cui3je94h280e1c7nae0\n11:25AM DBG Set network namespace by file netns=/var/lib/koker/netns/cui3je94h280e1c7nae0\n11:25AM DBG Mount target source=tmpfs target=dev\n11:25AM DBG Mount target source=proc target=proc\n11:25AM DBG Mount target source=sysfs target=sys\n11:25AM DBG Mount target source=tmpfs target=tmp\n11:25AM DBG Execute command command=sh container=cui3je94h280e1c7nae0\n/ # ip a\n1: lo: \u003cLOOPBACK,UP,LOWER_UP\u003e mtu 65536 qdisc noqueue state UNKNOWN qlen 1000\n    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00\n    inet 127.0.0.1/8 scope host lo\n       valid_lft forever preferred_lft forever\n    inet6 ::1/128 scope host\n       valid_lft forever preferred_lft forever\n53: eth0@if54: \u003cBROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN\u003e mtu 1500 qdisc noqueue state UP qlen 1000\n    link/ether 72:eb:c0:fd:87:0c brd ff:ff:ff:ff:ff:ff\n    inet 172.69.98.139/16 brd 172.69.255.255 scope global eth0\n       valid_lft forever preferred_lft forever\n    inet6 fe80::70eb:c0ff:fefd:870c/64 scope link\n       valid_lft forever preferred_lft forever\n/ # ping 8.8.8.8\nPING 8.8.8.8 (8.8.8.8): 56 data bytes\n64 bytes from 8.8.8.8: seq=0 ttl=111 time=21.645 ms\n^C\n--- 8.8.8.8 ping statistics ---\n1 packets transmitted, 1 packets received, 0% packet loss\nround-trip min/avg/max = 21.645/21.645/21.645 ms\n```\n\n- If you find logging is annoying, ignore them with \"--quiet\" option.\n\n```shell\n$ sudo koker -q container ls\nCONTAINER ID            IMAGE                   COMMAND\n\nccjuq1p3l1hn8clpgib0    0261ca8a4a79            sh\n\n$ sudo koker -q container run --hostname test --mem 1024 alpine sh\n/ #\n```\n\n- Note if you hit this kind of error, you should check the current max open files.\n\n```bash\n5:09PM ERR Something went wrong error=\"unable to pull image: unable to extract tarball's layer: open /var/lib/koker/images/2d389e545974d4a93ebdef09b650753a55f72d1ab4518d17a30c0e1b3e297444/31b3f1ad4ce1f369084d0f959813c51df0ca17d9877d5ee88c2db6ff88341430/usr/lib/x86_64-linux-gnu/perl-base/unicore/lib/Age/V80.pl: too many open files\"\n```\n\n```shell\n# Check current max open files\nulimit -n\n# Change value to appropriate value, for example 4096\nulimit -n 4096\n```\n\n## 4. Contributing\n\nPull requests and issues are alway welcome!\n","funding_links":["https://github.com/sponsors/ntk148v"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fntk148v%2Fkoker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fntk148v%2Fkoker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fntk148v%2Fkoker/lists"}