Skip to content

Commit b0a1563

Browse files
committed
Add file permissions demo
1 parent 185f214 commit b0a1563

File tree

6 files changed

+198
-0
lines changed

6 files changed

+198
-0
lines changed

Diff for: troubleshooting/file-permissions/Dockerfile

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
FROM docker.io/library/python:3.11-alpine
2+
3+
# Create a non-root user and group to run the app
4+
RUN addgroup -S apps && \
5+
adduser -S filebox -G apps
6+
7+
# Create a directory for file uploads
8+
RUN mkdir -p /app/uploads
9+
10+
COPY requirements.txt .
11+
RUN pip install -r requirements.txt
12+
13+
COPY app.py /app/app.py
14+
COPY templates/upload_form.html /app/templates/upload_form.html
15+
COPY templates/upload_success.html /app/templates/upload_success.html
16+
17+
# Change the user the container runs as
18+
USER filebox
19+
20+
CMD ["python", "/app/app.py"]

Diff for: troubleshooting/file-permissions/README.md

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Container Troubleshooting: File Permissions issues
2+
3+
This demo uses a fake web app called **Firebox** (sorry, Dropbox!) that allows you to upload files. The web app is written in Python using the Flask framework and runs in a Docker container.
4+
5+
## To run
6+
7+
```shell
8+
docker build -t file-permissions .
9+
10+
docker run -p 5000:5000 file-permissions
11+
```
12+
13+
Now access the web app in your web browser: <http://localhost:5000> and try to upload a file.
14+
15+
**You'll see that the file upload fails. But why?**
16+
17+
### To debug
18+
19+
To debug, we'll need to get the ID of the container. Run `docker ps` to get the container ID:
20+
21+
```shell
22+
docker ps
23+
```
24+
25+
Look for the container that has the image `file-permissions` and copy the container ID.
26+
27+
**Look at the logs.** First let's check out the logs:
28+
29+
```shell
30+
docker logs <container-id>
31+
```
32+
33+
We see something like this:
34+
35+
> 10.0.2.100 - - [10/Dec/2022 16:10:31] "POST /upload HTTP/1.1" 500 -
36+
> ...
37+
> PermissionError: [Errno 13] Permission denied: '/app/uploads/5sdfkhfweiohj.jpeg'
38+
39+
Oh no!
40+
41+
**Look at the folder inside the container.** Perhaps the folder inside the container will give us some clues. Start a shell inside the container and see what's going on.
42+
43+
```shell
44+
docker exec -it <container-id> sh
45+
```
46+
47+
Once inside the container, list the contents of the `/app/uploads` folder. Add the `-a` flag to show hidden files and folders and `-l` to show the permissions:
48+
49+
```shell
50+
/ $ ls -al /app/uploads
51+
total 0
52+
drwxr-xr-x 1 root root 0 Dec 10 16:09 .
53+
drwxr-xr-x 1 root root 18 Dec 10 16:10 ..
54+
```
55+
56+
The folder is empty! A-ha! But notice that the permissions are `drwxr-xr-x`, with the owner `root`. This means that the folder is owned by the `root` user, and that the `root` user has read, write, and execute permissions.
57+
58+
**Check: is our app running as root in the container?** Let's check the user that we're running as. Staying inside the shell in the container, run this command:
59+
60+
```bash
61+
/ $ whoami
62+
filebox
63+
```
64+
65+
So we're not running as `root`. But we are running as the `filebox` user. Let's check the permissions of the `filebox` user:
66+
67+
```bash
68+
/ $ id filebox
69+
uid=100(filebox) gid=101(apps) groups=101(apps),101(apps)
70+
```
71+
72+
The `filebox` user has a UID of 1000 and is in the `apps` group. It's not `root`.
73+
74+
Since the `uploads` folder is owned by the `root` user, the `filebox` user does not have permission to write to the folder.
75+
76+
### To fix
77+
78+
We can't just change the permissions of the folder inside the container while it's running, because the permissions will be reset when the container is restarted. We need to change the permissions of the folder every time the container starts.
79+
80+
**Change the permissions of the folder in the Dockerfile.** Let's change the permissions of the `uploads` folder by editing the _Dockerfile_. Replace the line that says `RUN mkdir -p /app/uploads` with this:
81+
82+
```dockerfile
83+
RUN mkdir -p /app/uploads && chown -R filebox:apps /app/uploads
84+
```
85+
86+
**Terminate and rebuild.** Now let's rebuild the image and run the container again. Press Ctrl+C to exit the container, then stop the container:
87+
88+
```bash
89+
docker stop <container-id>
90+
```
91+
92+
Then rebuild the image and run the container again:
93+
94+
```bash
95+
docker build -t file-permissions .
96+
97+
docker run -p 5000:5000 file-permissions
98+
```
99+
100+
Now try to upload a file again at <http://localhost:5000>
101+
102+
**Success!**
103+
104+
**Confirm the file was saved.** Let's start a shell inside the container and find the file in the `/app/uploads` folder. (Don't forget that you can get the container ID by running `docker ps`).
105+
106+
```bash
107+
docker exec -it <container-id> /bin/bash
108+
```
109+
110+
```bash
111+
/ $ ls -al /app/uploads/
112+
drwxr-xr-x 1 filebox apps 82 Dec 10 16:32 .
113+
drwxr-xr-x 1 root root 14 Dec 10 16:31 ..
114+
-rw-r--r-- 1 filebox apps 2036567 Dec 10 16:32 henlobird.jpeg
115+
```
116+
117+
## Wrapping up
118+
119+
In this demo, we saw how to debug a file permissions issue in a container.
120+
121+
We saw that the problem was that the app was trying to write to a folder that was owned by the `root` user, and that the app's user (`filebox`) didn't have permission to write to the folder. We fixed the problem by changing the permissions of the folder in the _Dockerfile_.
122+
123+
It's good security practice to run your container as a non-root user. But if you do, you can encounter some annoying permissions issues! So it's important to know how to debug these issues.
124+
125+

Diff for: troubleshooting/file-permissions/app.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from flask import Flask, request, render_template
2+
3+
# Create a new Flask app, giving the name of the current Python module
4+
app = Flask(__name__)
5+
6+
@app.route('/')
7+
def upload_form():
8+
return render_template('upload_form.html')
9+
10+
@app.route('/upload', methods=['POST'])
11+
def upload_file():
12+
if 'file' not in request.files:
13+
return 'No file part'
14+
15+
file = request.files['file']
16+
17+
if file.filename == '':
18+
return 'No file selected'
19+
20+
if file:
21+
file.save('/app/uploads/' + file.filename)
22+
23+
return render_template('upload_success.html', filename=file.filename)
24+
25+
if __name__ == '__main__':
26+
app.run(debug=True, host='0.0.0.0', port=5000)

Diff for: troubleshooting/file-permissions/requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
flask==2.2.2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<html>
2+
<head>
3+
<title>Upload a file</title>
4+
</head>
5+
6+
<body>
7+
<h1>Welcome to Filebox!</h1>
8+
<h2>Please upload a file</h2>
9+
<p>Please note that all files will be deleted when this application is restarted.</p>
10+
<form action="/upload" method="post" enctype="multipart/form-data">
11+
<input type="file" name="file" />
12+
<input type="submit" value="Upload" />
13+
</form>
14+
</body>
15+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<html>
2+
<head>
3+
<title>Thank you</title>
4+
</head>
5+
6+
<body>
7+
<h1>Chomp!</h1>
8+
<p>Thanks for your file, it's been safely uploaded.</p>
9+
<p><a href="/">Upload another file</a></p>
10+
</body>
11+
</html>

0 commit comments

Comments
 (0)