Tecnativa's docker-socket-proxy grants three API sections and hides its own version
Proxy over your Docker socket to restrict which requests it accepts
At a glance
- What is it?
- An HAProxy image that sits in front of the Docker socket and denies requests by environment variable. POST=0 is the switch that makes the whole API read only, and the image it builds calls itself 1.0.0 while the newest published tag is 0.5.0.
- Who is it for?
- This proxy fits the common case: one service on one Docker network needs a narrow slice of the Docker API, and everything else should get a 403. Check four things before you rely on it.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 35 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 5, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The 403 you get back is HAProxy's page, not Docker's
The implementation is deliberately small: the official Alpine-based HAProxy image plus a configuration file. Requests are matched against environment variables and anything disallowed comes back as `HTTP 403 Forbidden`. The body of that response is worth reading, because it explains a confusing symptom. The worked example in the README shows `docker container ls` printing an error that begins `Error response from daemon:` and continues with an HTML page reading 403 Forbidden and Request forbidden by administrative rules. That HTML is HAProxy's own deny page, and the daemon prefix is the Docker client adding its own label. The request never reached the Docker daemon at all. So when you see a daemon error carrying an HTML body, you are looking at the proxy refusing the call, not at a container or daemon failure.
POST=0 is the switch that makes the whole API read only
The per-section variables decide what a service can see, and they are named after the URL prefix they govern, so `AUTH` blocks `/auth/*` and the rest follow the same pattern. Each one takes `0` to revoke or `1` to grant. The variable that behaves differently is `POST`. When it is disabled, only `GET` and `HEAD` operations are allowed, which means any section of the API is read-only. That single setting is the difference between a service that can inspect the host and a service that can also change it, and it applies regardless of which sections you have granted. Two more sit in the security-critical group that the README says to enable with maximum caution: `AUTH` and `SECRETS`. Three are granted by default because they are described as mostly harmless and almost required for any service using the API: `EVENTS`, `PING` and `VERSION`.
The proxy needs --privileged and ships no TLS, by design
The security recommendations and the run command pull in opposite directions, and the README is direct about it. The container has to be started with `--privileged`, and the stated reason is that it connects with the Docker socket, which is a privileged connection in some SELinux and AppArmor contexts and would get locked otherwise. At the same time the image includes no TLS support at all, just a plain HTTP proxy to the host Docker socket, which is not TLS protected even if you configured the host for TLS protection. The README calls that by design and says access is meant to be restricted through Docker's built-in firewall. The run command reflects that intent by publishing the port on loopback only:
docker container run \
-d --privileged \
--name dockerproxy \
-v /var/run/docker.sock:/var/run/docker.sock \
-p 127.0.0.1:2375:2375 \
tecnativa/docker-socket-proxyA client then points at it with `export DOCKER_HOST=tcp://localhost:2375`. The first recommendation in the list is never to expose this port to a public network, only to a Docker network where the proxy and its one service reside.
Thirty defaults in the Dockerfile, and two named variables are not among them
The Dockerfile is where the real default set lives, and it is worth reading next to the README's lists. It sets thirty environment variables. Three are granted: `EVENTS=1`, `PING=1` and `VERSION=1`. Everything else is `0`, with two exceptions that carry values instead of a flag: `LOG_LEVEL=info` and `SOCKET_PATH=/var/run/docker.sock`. Two variables the README names are missing from that block. `ALLOW_PAUSE` and `ALLOW_UNPAUSE` appear in the not-always-needed list with their endpoints spelled out as `containers/`id`/`pause` and `containers/`id`/`unpause`, but the Dockerfile sets no default for either. One variable that is set is missing from the README's lists: `DISABLE_IPV6=0`. So the documented set and the shipped set differ in both directions, which is the kind of gap that only shows up when you read the two files side by side. One more detail about the naming: `ALLOW_RESTARTS` is documented as covering `stop`, `restart` and `kill` together, so one flag governs three endpoints.
LOG_LEVEL takes syslog names, and one of them is err
The logging knob is a single variable, `LOG_LEVEL`, defaulting to info, and its accepted values are the syslog severity names rather than the words a developer would guess: debug, info, notice, warning, err, crit, alert and emerg. Note that it is `err` and not `error`, and that `warn` does not appear in place of `warning`. This is the same naming the underlying HAProxy uses, which is consistent but easy to get wrong when you are reading the value out of a compose file. Inside the image, `docker-entrypoint.sh` is copied to `/usr/local/bin/` and the configuration is copied in as a template at `/usr/local/etc/haproxy/haproxy.cfg.template`, while the container is started with `CMD haproxy -f /tmp/haproxy.cfg`. The template is rendered at startup into the path the command actually reads, and the container declares port 2375 and runs as root so HAProxy can reach the mounted socket.
Six API versions are supported, and the sample output is from 2017
The supported API versions are listed as 1.27, 1.28, 1.29, 1.30, 1.37 and 1.51. The gaps are as informative as the entries: nothing between 1.30 and 1.37, and nothing between 1.37 and 1.51. If your host runs an API version in one of those gaps, this list does not tell you what happens, and the README's own instruction on the subject is to read the docs for the version you are using and know what you are doing. The usage example is older than the list. The `docker version` output in the walkthrough reports version 17.03.1-ce, API version 1.27, go1.7.5, built Mon Mar 27 17:14:43 2017. So the lowest supported version is also the one the example was captured on, while the highest entry, 1.51, postdates every line of that example. Reading the walkthrough as current documentation is a mistake the dates make obvious.
The project file says 1.0.0 and the newest release tag is 0.5.0
The Python side of the repository is a development harness, not a distributable package, and `pyproject.toml` says so with `package-mode = false`. That same file declares `version = "1.0.0"` and an empty description, while the published releases are v0.5.0 on 2026-07-27, v0.4.2 on 2025-12-16 and v0.4.1 on 2025-09-05. So the number in the project file and the number on the newest image tag do not match, and `poetry install` reports a version you cannot pull. The dependency block explains why it hardly matters: the only runtime dependency is `python = "^3.8"`, and everything else is development tooling, namely black at `^20.8b1` with prereleases allowed, flake8 at `^3.8.4`, plumbum, pytest at `^6.1.2` and pytest-xdist. A formatting tool pinned to a beta and a test runner pinned to version 6 are conservative choices for a repository whose last push is dated 2026-08-31.
The repository is generated from a template, and the tag rules stop mid-sentence
The top level shows how the project is maintained. There is a `.copier-answers.image-template.yml` and a `.copier-answers.autopretty.yml`, both of which are answer files written by copier, and the badges at the top of the README link to `Tecnativa/image-template` pinned at tag v0.1.3. So this repository is not written by hand from nothing; it is generated from a company template, which is why the housekeeping files sit alongside `.flake8`, `.pre-commit-config.yaml`, `.prettierrc.yml` and `.editorconfig`. What the template does not generate is judgement about which variables to document. The image tag section states three rules, that each released git version produces a matching tag, that `:latest` refers to the latest released version, and that `:edge` is whatever is in master, and then stops partway through a fourth sentence, at the words Any other tag you find in o. For testing, the image is named `docker-socket-proxy:local` by default, `poetry run pytest --prebuild` builds it first, and `DOCKER_IMAGE_NAME` swaps in an image you built yourself.
Editorial conclusion
This proxy fits the common case: one service on one Docker network needs a narrow slice of the Docker API, and everything else should get a 403. Check four things before you rely on it. Read your own API version against the supported list, because it names six versions and skips ranges in between. Decide whether POST=0 is enough on its own or whether a section such as CONTAINERS or VOLUMES has to be readable. Keep the published port on loopback, because the image has no TLS and says so. And if you run the tests, note that the image name is docker-socket-proxy:local by default, the flag is `--prebuild`, and DOCKER_IMAGE_NAME overrides both.
Frequently asked questions
what is docker socket proxy
Tecnativa's docker-socket-proxy is a security-enhanced proxy for the Docker socket, built from the official Alpine-based HAProxy image with a small configuration file. It blocks Docker API requests according to environment variables you set and returns HTTP 403 Forbidden for the ones you have revoked.
how to setup docker socket proxy
Run the image with `--privileged`, mount `/var/run/docker.sock` into it and publish port 2375 on loopback, then point a client at it with `export DOCKER_HOST=tcp://localhost:2375`. If your OS keeps the socket elsewhere, set the `SOCKET_PATH` variable, for example to `/var/run/balena-engine.sock` on balenaOS.
how to use docker socket proxy
Grant and revoke API sections through environment variables whose names match the URL prefix, using `0` to revoke and `1` to grant. `EVENTS`, `PING` and `VERSION` are granted by default, and setting `POST=0` leaves only `GET` and `HEAD` allowed so any section you grant is read-only.
install docker socket proxy
There is nothing to install for use, since the README pulls the published image `tecnativa/docker-socket-proxy`. Only development needs a setup step, and that one is `poetry install`, with the tests run afterwards through `poetry run pytest --prebuild`.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/tecnativa-docker-socket-proxy)