150

[Updated1] I have a shell which will change TCP kernel parameters in some functions, but now I need to make this shell run in Docker container, that means, the shell need to know it is running inside a container and stop configuring the kernel.

Now I'm not sure how to achieve that, here is the contents of /proc/self/cgroup inside the container:

9:hugetlb:/
8:perf_event:/
7:blkio:/
6:freezer:/
5:devices:/
4:memory:/
3:cpuacct:/
2:cpu:/docker/25ef774c390558ad8c4e9a8590b6a1956231aae404d6a7aba4dde320ff569b8b
1:cpuset:/

Any flags above can I use to figure out if this process is running inside a container?

[Updated2]: I have also noticed Determining if a process runs inside lxc/Docker, but it seems not working in this case, the content in /proc/1/cgroup of my container is:

8:perf_event:/
7:blkio:/
6:freezer:/
5:devices:/
4:memory:/
3:cpuacct:/
2:cpu:/docker/25ef774c390558ad8c4e9a8590b6a1956231aae404d6a7aba4dde320ff569b8b
1:cpuset:/

No /lxc/containerid

peterh
  • 11,875
  • 18
  • 85
  • 108
harryz
  • 4,980
  • 6
  • 31
  • 34

13 Answers13

188

Docker creates .dockerenv and .dockerinit (removed in v1.11) files at the top of the container's directory tree so you might want to check if those exist.

Something like this should work.

#!/bin/bash
if [ -f /.dockerenv ]; then
    echo "I'm inside matrix ;(";
else
    echo "I'm living in real world!";
fi
ReactiveRaven
  • 7,203
  • 2
  • 29
  • 38
at0S
  • 4,522
  • 2
  • 14
  • 10
  • 3
    Unless, of course, you or someone else has created `/.dockerinit` on your host (perhaps by accident), in which case it will be wrong outside a container. – sosiouxme Nov 25 '15 at 22:05
  • 27
    If somebody else made it in / then they are root and you've got worse problems than knowing whether you are in docker or no. – gm3dmo Nov 30 '15 at 15:55
  • 26
    Beware relying on `/.dockerenv` in the long term. It is [not intended to be used this way](https://github.com/docker/docker/issues/18355#issuecomment-220484748). – ReactiveRaven Aug 23 '16 at 09:39
  • 1
    fwiw, Podman does not create `/.dockerenv`. It does create `/run/.containerenv` but by similar logic, sounds like implementation detail not to be relied upon. See https://github.com/containers/libpod/issues/3586 for some podman-specific alternatives. – Beni Cherniavsky-Paskin Mar 12 '20 at 13:30
  • just an alternative way of writing the above test `test -f /.dockerenv && echo inside docker || echo not inside docker` – Prosunjit Biswas Apr 28 '22 at 05:48
  • Does this work in windows container? – cowlinator Jun 02 '22 at 00:44
  • 2
    @cowlinator Windows containers are even easier, just check `HKLM\System\CurrentControlSet\Control\ContainerType`, any value means you're in a Windows container. – Mark Lopez Jan 04 '23 at 21:48
99

To check inside a Docker container if you are inside a Docker container or not can be done via /proc/1/cgroup. As this post suggests you can to the following:

Outside a docker container all entries in /proc/1/cgroup end on / as you can see here:

vagrant@ubuntu-13:~$ cat /proc/1/cgroup
11:name=systemd:/
10:hugetlb:/
9:perf_event:/
8:blkio:/
7:freezer:/
6:devices:/
5:memory:/
4:cpuacct:/
3:cpu:/
2:cpuset:/

Inside a Docker container some of the control groups will belong to Docker (or LXC):

vagrant@ubuntu-13:~$ docker run busybox cat /proc/1/cgroup
11:name=systemd:/
10:hugetlb:/
9:perf_event:/
8:blkio:/
7:freezer:/
6:devices:/docker/3601745b3bd54d9780436faa5f0e4f72bb46231663bb99a6bb892764917832c2
5:memory:/
4:cpuacct:/
3:cpu:/docker/3601745b3bd54d9780436faa5f0e4f72bb46231663bb99a6bb892764917832c2
2:cpuset:/
Community
  • 1
  • 1
Thomas Uhrig
  • 30,811
  • 12
  • 60
  • 80
  • 6
    it is not strictly true that "Outside a docker container all entries in /proc/1/cgroup end on /". On ubuntu 16.04 for example I have: `12:perf_event:/ 11:blkio:/init.scope 10:cpuset:/ 9:devices:/init.scope 8:hugetlb:/ 7:cpu,cpuacct:/init.scope 6:net_cls,net_prio:/ 5:memory:/init.scope 4:pids:/init.scope 3:rdma:/ 2:freezer:/ 1:name=systemd:/init.scope ` – samfr May 09 '18 at 23:50
  • 4
    This pretty much only works on Linux, not on Darwin or other BSDs that don't even use a procfs. – Christian Dec 16 '19 at 10:40
  • 2
    @Christian Docker/LXC are Linux only things, so that's fine, right :)? – Robert Lacroix Jan 09 '20 at 19:54
  • @RobertLacroix so you're saying if you don't find a procfs, you're not in Docker? Well, that's fair enough I guess... – Christian Jan 13 '20 at 11:27
  • pid 1 won't work when container is being started with host's pid namespace ( `docker run --pid=host`) thus `/proc/self/cgroups` or `/proc/$$/cgroups` is better i suppose. – Kevin Chan Jul 14 '22 at 09:36
  • 5
    `cat /proc/1/cgroup` merely outputs `0::/` while in a Docker container. Can anyone tell me what happened? – ipid Dec 20 '22 at 12:03
  • @ipid are you trying from inside a Docker container on a macOS host? Because I see the same thing inside containers running on a macOS host. Checking for the existence of `/.dockerenv` works on macOS, though. – dossy Apr 08 '23 at 01:42
  • As [stated here](https://www.baeldung.com/linux/is-process-running-inside-container), you might also want to try: `cat /proc/self/mountinfo` If it works, that’s because it’s using control group v2, instead of v1 (which uses /proc/1/cgroup). – Andorkan Jul 15 '23 at 17:47
36

We use the proc's sched (/proc/$PID/sched) to extract the PID of the process. The process's PID inside the container will differ then it's PID on the host (a non-container system).

For example, the output of /proc/1/sched on a container will return:

root@33044d65037c:~# cat /proc/1/sched | head -n 1
bash (5276, #threads: 1)

While on a non-container host:

$ cat /proc/1/sched  | head -n 1
init (1, #threads: 1)

This helps to differentiate if you are in a container or not. eg you can do:

if [[ ! $(cat /proc/1/sched | head -n 1 | grep init) ]]; then {
    echo in docker
} else {
    echo not in docker
} fi
Hugh Perkins
  • 7,975
  • 7
  • 63
  • 71
Founder
  • 541
  • 5
  • 6
  • 1
    this is actually quite a valuable information. thanks – Fabian Lange Jun 15 '16 at 20:58
  • 6
    Depending on the OS, "init" might need to be replaced with "systemd". More information on systemd [here](https://www.tecmint.com/systemd-replaces-init-in-linux/). – BrianV Jun 23 '17 at 01:53
  • 3
    As mentioned by @BrianV, this doesn't work for me too. – Shubham Chaudhary Aug 03 '17 at 07:09
  • 5
    In a Docker container running on a k8s cluster, `head -n1 /proc/1/sched` returns `dumb-init (1, #threads: 1)`, so the check suggested in this answer fails. (Also, contrary to what the answer suggests, the PID is shown as "1" in that line although I'm doing this in a container.) – Stefan Majewsky Feb 12 '18 at 13:50
  • This is definitely not a universal solution. You can (sort of) use whatever you want for a container's PID 1. E.g. if you do `docker run --init ...` it will be `docker-init`. If you do e.g. `docker run ... head -n 1 /proc/1/sched` it will be `head`. – jpkotta Apr 13 '20 at 21:42
  • This method is not durable. – daparic May 29 '20 at 09:11
  • Here's a shorter version and without violating any [shellcheck](https://www.shellcheck.net/) rules: `EXECUTABLE_NAME=$(if head -n 1 /proc/1/sched | grep -q 'tini'; then echo "docker run [name of this container]"; else echo "${0}"; fi)`. This assumes that you're using [tini](https://github.com/krallin/tini) in your container, of course. – Nick Ribal Dec 24 '20 at 05:28
  • 1
    This answer is the only one that still works for Docker v20. Simplest code to wrap it (exit code 0 if running with different _init_ than systemd or init: `awk '{exit ($1 ~ /^init|systemd$/)}' /proc/1/sched` – Kokos Mar 09 '21 at 14:45
  • Instead of `cat /proc/1/sched | head -n 1` you can simply use `head -n 1 /proc/1/sched`. – Wolfram Rösler May 06 '22 at 13:12
  • 1
    Note that this does not appear to work any longer. At least on ubuntu Jammy (kernel 5.15.0 and docker 20.10.12) the pid in the top line of /proc/nnn/sched is now the PID from inside the container when viewed in the container. – cabbey Nov 21 '22 at 21:44
33

Using Environment Variables

For my money, I prefer to set an environment variable inside the docker image that can then be detected by the application.

For example, this is the start of a demo Dockerfile config:

FROM node:12.20.1 as base
ENV DOCKER_RUNNING=true
RUN yarn install --production
RUN yarn build

The second line sets an envar called DOCKER_RUNNING that is then easy to detect. The issue with this is that in a multi-stage build, you will have to repeat the ENV line every time you FROM off of an external image. For example, you can see that I FROM off of node:12.20.1, which includes a lot of extra stuff (git, for example). Later on in my Dockerfile I then COPY things over to a new image based on node:12.20.1-slim, which is much smaller:

FROM node:12.20.1-slim as server
ENV DOCKER_RUNNING=true
EXPOSE 3000
COPY --from=base /build /build
CMD ["node", "server.js"]

Even though this image target server is in the same Dockerfile, it requires the ENV var to be defined again because it has a different base image.

If you make use of Docker-Compose, you could instead easily define an envar there. For example, your docker-compose.yml file could look like this:

version: "3.8"
services:
  nodeserver:
    image: michaeloryl/stackdemo
    environment:
      - NODE_ENV=production
      - DOCKER_RUNNING=true
Michael Oryl
  • 20,856
  • 14
  • 77
  • 117
  • 1
    This is great, just being explicit – four43 Jun 14 '21 at 20:58
  • 1
    Beware that some programs do purge environment when starting (e.g shiny server). The variables are there if you open a shell, but empty within the program. In that case check out how to configure those vars for that particular software. – Ott Toomet Jun 02 '22 at 16:41
24

Thomas' solution as code:

running_in_docker() {
  (awk -F/ '$2 == "docker"' /proc/self/cgroup | read non_empty_input)
}

Note

The read with a dummy variable is a simple idiom for Does this produce any output?. It's a compact method for turning a possibly verbose grep or awk into a test of a pattern.

Additional note on read

Community
  • 1
  • 1
Henk Langeveld
  • 8,088
  • 1
  • 43
  • 57
  • 11
    Except...this will fail in some envrionments, because, e.g., `3:cpu,cpuacct:/system.slice/docker-1ce79a0dec4a2084d54acf187a1e177e0339dc90d0218b48b4456576ecaf291e.scope` would not match. Simpler to `grep -q docker /proc/1/cgroup`; the result code from that should also be sufficient. – larsks Mar 08 '15 at 20:27
  • 2
    `read` might work for `bash`, but in the most used `dash` shell you have to use either `read dummy` (or similar) or use a construct like `[ -n "$(command)" ]` – Daniel Alder Oct 06 '15 at 08:47
  • @DanielAlder Good catch, Daniel. I will update the text. – Henk Langeveld Oct 06 '15 at 19:00
  • 1
    Previously this claimed that any Bourne compatible shell supports the plain `read` without variable name. This is only true for bash and ksh93. The Opengroup only specifies `read var` and does not mention `read` behaviour without at least one variable. In _bash_ and _ksh93_, if no _var_ is given, read uses the shell variable `REPLY`. – Henk Langeveld Oct 06 '15 at 19:12
  • @HenkLangeveld This would fix your solution: (awk -F: '$3 ~ /docker/' /proc/self/cgroup | read non_empty_input) – Paul May 25 '16 at 13:39
  • 1
    Why can't we just use `awk -F: '$3 ~ /docker/' /proc/self/cgroup | read`? Works for me. – Shubham Chaudhary Aug 03 '17 at 07:17
  • @larsks Indeed, `grep` is simpler. However, using `-q` didn't work on my docker container (command returned nothing) so it's better to omit it. – Frak Apr 08 '20 at 14:42
  • @frakman1 the `grep -q` command is not expected to return anything. See the `grep(1)` man page. – larsks Apr 08 '20 at 15:32
17

What works for me is to check for the inode number of the '/.' Inside the docker, its a very high number. Outside the docker, its a very low number like '2'. I reckon this approach would also depend on the FileSystem being used.

Example

Inside the docker:

# ls -ali / | sed '2!d' |awk {'print $1'}
1565265

Outside the docker

$ ls -ali / | sed '2!d' |awk {'print $1'}
2

In a script:

#!/bin/bash
INODE_NUM=`ls -ali / | sed '2!d' |awk {'print $1'}`
if [ $INODE_NUM == '2' ];
then
        echo "Outside the docker"
else
        echo "Inside the docker"
fi
trohit
  • 319
  • 3
  • 6
3

We needed to exclude processes running in containers, but instead of checking for just docker cgroups we decided to compare /proc/<pid>/ns/pid to the init system at /proc/1/ns/pid. Example:

pid=$(ps ax | grep "[r]edis-server \*:6379" | awk '{print $1}')
if [ $(readlink "/proc/$pid/ns/pid") == $(readlink /proc/1/ns/pid) ]; then
   echo "pid $pid is the same namespace as init system"
else
   echo "pid $pid is in a different namespace as init system"
fi

Or in our case we wanted a one liner that generates an error if the process is NOT in a container

bash -c "test -h /proc/4129/ns/pid && test $(readlink /proc/4129/ns/pid) != $(readlink /proc/1/ns/pid)"

which we can execute from another process and if the exit code is zero then the specified PID is running in a different namespace.

Greg Bray
  • 14,929
  • 12
  • 80
  • 104
  • Does not work for me. From within a k8s-scheduled Docker container, `readlink /proc/self/ns/pid` and `readlink /proc/1/ns/pid` produce the same output. – Stefan Majewsky Feb 12 '18 at 13:52
  • 1
    @StefanMajewsky Might want to try using https://github.com/jessfraz/amicontained to see what features are enabled in the container runtime. – Greg Bray Feb 13 '18 at 16:17
3

Based on Dan Walsh's comment about using SELinux ps -eZ | grep container_t, but without requiring ps to be installed:

$ podman run --rm fedora:31 cat /proc/1/attr/current
system_u:system_r:container_t:s0:c56,c299
$ podman run --rm alpine cat /proc/1/attr/current
system_u:system_r:container_t:s0:c558,c813
$ docker run --rm fedora:31 cat /proc/1/attr/current
system_u:system_r:container_t:s0:c8,c583
$ cat /proc/1/attr/current
system_u:system_r:init_t:s0

This just tells you you're running in a container, but not which runtime.

Didn't check other container runtimes but https://opensource.com/article/18/2/understanding-selinux-labels-container-runtimes provides more info and suggests this is widely used, might also work for rkt and lxc?

Beni Cherniavsky-Paskin
  • 9,483
  • 2
  • 50
  • 58
3

golang code, via the /proc/%s/cgroup to check a process in a docker,include the k8s cluster

func GetContainerID(pid int32) string {
    cgroupPath := fmt.Sprintf("/proc/%s/cgroup", strconv.Itoa(int(pid)))
    return getContainerID(cgroupPath)
}

func GetImage(containerId string) string {
    if containerId == "" {
        return ""
    }
    image, ok := containerImage[containerId]
    if ok {
        return image
    } else {
        return ""
    }
}
func getContainerID(cgroupPath string) string {
    containerID := ""
    content, err := ioutil.ReadFile(cgroupPath)
    if err != nil {
        return containerID
    }
    lines := strings.Split(string(content), "\n")
    for _, line := range lines {
        field := strings.Split(line, ":")
        if len(field) < 3 {
            continue
        }
        cgroup_path := field[2]
        if len(cgroup_path) < 64 {
            continue
        }
        // Non-systemd Docker
        //5:net_prio,net_cls:/docker/de630f22746b9c06c412858f26ca286c6cdfed086d3b302998aa403d9dcedc42
        //3:net_cls:/kubepods/burstable/pod5f399c1a-f9fc-11e8-bf65-246e9659ebfc/9170559b8aadd07d99978d9460cf8d1c71552f3c64fefc7e9906ab3fb7e18f69
        pos := strings.LastIndex(cgroup_path, "/")
        if pos > 0 {
            id_len := len(cgroup_path) - pos - 1
            if id_len == 64 {
                //p.InDocker = true
                // docker id
                containerID = cgroup_path[pos+1 : pos+1+64]
                // logs.Debug("pid:%v in docker id:%v", pid, id)
                return containerID
            }
        }
        // systemd Docker
        //5:net_cls:/system.slice/docker-afd862d2ed48ef5dc0ce8f1863e4475894e331098c9a512789233ca9ca06fc62.scope
        docker_str := "docker-"
        pos = strings.Index(cgroup_path, docker_str)
        if pos > 0 {
            pos_scope := strings.Index(cgroup_path, ".scope")
            id_len := pos_scope - pos - len(docker_str)
            if pos_scope > 0 && id_len == 64 {
                containerID = cgroup_path[pos+len(docker_str) : pos+len(docker_str)+64]
                return containerID
            }
        }
    }
    return containerID
}
wcc526
  • 3,915
  • 2
  • 31
  • 29
0

What works for me, as long as I know the system programs/scrips will be running on, is confirming if what's running with PID 1 is systemd (or equivalent). If not, that's a container. And this should be true for any linux container, not only docker.

cpr4t3s
  • 1,345
  • 1
  • 11
  • 15
0

The /.dockerenv file seems to not exist when using GitPod, so I used the following check extra to @at0S's answer:

if [ -f /.dockerenv ] | [ -n "$(env | grep "^GITPOD")" ]; then
  echo "In Docker"
else
  echo "Outside Docker"
fi
M. Rodo
  • 448
  • 4
  • 12
0

Process with pid 1 will be different between container and host:

  • On Linux host systems it will be systemd or init
  • In Docker container it will be the process that is run by the container, but not systemd or init

Check process name with pid 1

$ ps -p 1 -o comm=
SaturnIC
  • 17
  • 4
-1

Had the need for this capability in 2022 on macOS and only the answer by @at0S still works from all the other options.

  • /proc/1/cgroup only has the root directory in a container unless configured otherwise
  • /proc/1/sched showed the same in-container process number. The name was different (bash) but that's not very portable.
  • Environment variables work if you configure your container yourself, but none of the default environment variables helped

I did find an option not listed in the other answers: /proc/1/mounts included an overlay filesystem with "docker" in its path.

Daniel Widdis
  • 8,424
  • 13
  • 41
  • 63