Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/events/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
__pycache__
*.pyc
.git
.env
*.md
.vscode
2 changes: 2 additions & 0 deletions app/events/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY main.py .
RUN addgroup --system app && adduser --system --ingroup app app && chown -R app:app /app
USER app

EXPOSE 8081
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8081"]
6 changes: 6 additions & 0 deletions app/gateway/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
__pycache__
*.pyc
.git
.env
*.md
.vscode
2 changes: 2 additions & 0 deletions app/gateway/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY main.py .
RUN addgroup --system app && adduser --system --ingroup app app && chown -R app:app /app
USER app

EXPOSE 8080
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]
6 changes: 6 additions & 0 deletions app/payments/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
__pycache__
*.pyc
.git
.env
*.md
.vscode
2 changes: 2 additions & 0 deletions app/payments/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY main.py .
RUN addgroup --system app && adduser --system --ingroup app app && chown -R app:app /app
USER app

EXPOSE 8082
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8082"]
222 changes: 222 additions & 0 deletions submissions/lab2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
# Lab 2

## Task 1

I checked the Docker images first.

```bash
docker images | grep app
```

```text
app-events:latest af008b8239e4 232MB 56.9MB
app-gateway:latest fec898af8ff0 213MB 51.9MB
app-payments:latest 6dc7367db006 211MB 51.4MB
```

The biggest image was `app-events`. I also looked at `app-gateway` layer history.

```bash
docker history app-gateway --no-trunc --format "table {{.CreatedBy}}\t{{.Size}}"
```

```text
CMD ["uvicorn" "main:app" "--host" "0.0.0.0" "--port" "8080"] 0B
EXPOSE [8080/tcp] 0B
COPY main.py . # buildkit 24.6kB
RUN /bin/sh -c pip install --no-cache-dir -r requirements.txt 28.9MB
COPY requirements.txt . # buildkit 12.3kB
WORKDIR /app 8.19kB
CMD ["python3"] 0B
RUN /bin/sh -c set -eux; for src in idle3 ... 16.4kB
RUN /bin/sh -c set -eux; savedAptMark=... 39.9MB
ENV PYTHON_SHA256=... 0B
ENV PYTHON_VERSION=3.13.12 0B
ENV GPG_KEY=7169605F62C751356D054A26A821E680E5FA6305 0B
```

`app-gateway` has 15 layers. The biggest layer is the big Python base image build step at `39.9MB`. The biggest app-specific layer is `pip install` at `28.9MB`, because that is where the Python packages are added.

Then I checked the containers on the Docker network.

```bash
docker inspect app-events-1 --format '{{.Name}} {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
docker inspect app-gateway-1 --format '{{.Name}} {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
docker inspect app-payments-1 --format '{{.Name}} {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
```

```text
/app-events-1 172.28.0.5
/app-gateway-1 172.28.0.6
/app-payments-1 172.28.0.2
```

```bash
docker inspect app-payments-1 --format '{{range .Config.Env}}{{println .}}{{end}}'
```

```text
PAYMENT_LATENCY_MS=0
PAYMENT_FAILURE_RATE=0.0
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
GPG_KEY=7169605F62C751356D054A26A821E680E5FA6305
PYTHON_VERSION=3.13.12
PYTHON_SHA256=2a84cd31dd8d8ea8aaff75de66fc1b4b0127dd5799aa50a64ae9a313885b4593
```

After that I went inside the gateway container.

```bash
docker exec app-gateway-1 whoami
docker exec app-gateway-1 id
docker exec app-gateway-1 cat /etc/resolv.conf
docker exec app-gateway-1 python3 -c "import urllib.request; print(urllib.request.urlopen('http://events:8081/health').read().decode())"
docker exec app-gateway-1 python3 -c "import urllib.request; print(urllib.request.urlopen('http://payments:8082/health').read().decode())"
```

```text
root
uid=0(root) gid=0(root) groups=0(root)

# Generated by Docker Engine.
# This file can be edited; Docker Engine will not make further changes once it
# has been modified.

nameserver 127.0.0.11
options ndots:0

{"status":"healthy","checks":{"postgres":"ok","redis":"ok"}}
{"status":"healthy","failure_rate":0.0,"latency_ms":0}
```

So before the optimization part, the gateway container was running as `root`. DNS inside the container uses Docker's internal resolver at `127.0.0.11`.

I also checked the service logs and made one request flow.

```bash
curl -s http://localhost:3080/events > /dev/null
curl -s -X POST http://localhost:3080/events/1/reserve -H "Content-Type: application/json" --data-binary '{"quantity":1}'
docker compose logs gateway --tail=10
docker compose logs events --tail=10
```

```text
gateway-1 | {"time":"2026-06-12 20:38:13,112","level":"INFO","service":"gateway","msg":"HTTP Request: GET http://events:8081/events "HTTP/1.1 200 OK""}
gateway-1 | INFO: 172.28.0.1:48642 - "GET /events HTTP/1.1" 200 OK
gateway-1 | {"time":"2026-06-12 20:38:13,170","level":"INFO","service":"gateway","msg":"HTTP Request: POST http://events:8081/events/1/reserve "HTTP/1.1 200 OK""}
gateway-1 | INFO: 172.28.0.1:48652 - "POST /events/1/reserve HTTP/1.1" 200 OK
events-1 | INFO: 172.28.0.6:47654 - "GET /events HTTP/1.1" 200 OK
events-1 | {"time":"2026-06-12 20:38:13,169","level":"INFO","service":"events","msg":"Reserved 1 tickets for event 1: 1f852b86-1a1b-4313-977b-3a4eb7573f76"}
events-1 | INFO: 172.28.0.6:47654 - "POST /events/1/reserve HTTP/1.1" 200 OK
```

This was enough to follow one request from `gateway` to `events` by timestamp.

I also checked the Docker network itself.

```bash
docker network ls | grep app
docker network inspect app_default --format '{{range .Containers}}{{.Name}}: {{.IPv4Address}}{{"\n"}}{{end}}'
```

```text
73ca602c3a15 app_default bridge local

app-postgres-1: 172.28.0.4/16
app-gateway-1: 172.28.0.6/16
app-redis-1: 172.28.0.3/16
app-events-1: 172.28.0.5/16
app-payments-1: 172.28.0.2/16
```

The gateway finds the `events` service through Docker Compose DNS. Inside the container, the name `events` is resolved by `127.0.0.11`, and in this run it resolved to `172.28.0.5`.

## Task 2

I added the same `.dockerignore` file to `app/gateway`, `app/events`, and `app/payments`.

```text
__pycache__
*.pyc
.git
.env
*.md
.vscode
```

Image sizes before rebuild:

```text
app-events:latest af008b8239e4 232MB 56.9MB
app-gateway:latest fec898af8ff0 213MB 51.9MB
app-payments:latest 6dc7367db006 211MB 51.4MB
```

Image sizes after rebuild:

```text
app-events:latest e189a1c71f13 233MB 56.9MB
app-gateway:latest 66cb7b15061b 213MB 51.9MB
app-payments:latest 287897942389 211MB 51.4MB
```

The size change was basically zero here. That makes sense because the build context was already very small.

I also changed all three Dockerfiles to add a non-root user and switch to it.

Files: `app/gateway/Dockerfile`, `app/events/Dockerfile`, `app/payments/Dockerfile`
Line: before `EXPOSE` and `CMD`

```dockerfile
RUN addgroup --system app && adduser --system --ingroup app app && chown -R app:app /app
USER app
```

This creates a non-root user called `app`, gives it access to `/app`, and runs the container as that user instead of `root`.

After rebuild, `whoami` inside the gateway container changed from `root` to `app`.

```bash
docker exec app-gateway-1 whoami
```

```text
app
```

## Bonus Task

I cleared the logs, ran one full purchase flow, and then captured `docker compose logs --timestamps`.

Reserve output:

```json
{"reservation_id":"6d2f601b-155a-4a52-96c3-2a108044aff4","event_id":1,"quantity":1,"total_cents":5000,"expires_in_seconds":300}
```

Pay output:

```json
{"order_id":"6d2f601b-155a-4a52-96c3-2a108044aff4","event_id":1,"quantity":1,"total_cents":5000,"status":"confirmed"}
```

Timestamped log flow:

```text
events-1 | 2026-06-12T20:40:56.378886950Z {"time":"2026-06-12 20:40:56,378","level":"INFO","service":"events","msg":"Reserved 1 tickets for event 1: 6d2f601b-155a-4a52-96c3-2a108044aff4"}
gateway-1 | 2026-06-12T20:40:56.380978684Z {"time":"2026-06-12 20:40:56,380","level":"INFO","service":"gateway","msg":"HTTP Request: POST http://events:8081/events/1/reserve "HTTP/1.1 200 OK""}
gateway-1 | 2026-06-12T20:40:56.381938864Z INFO: 172.28.0.1:36392 - "POST /events/1/reserve HTTP/1.1" 200 OK
payments-1 | 2026-06-12T20:40:56.639541159Z {"time":"2026-06-12 20:40:56,639","level":"INFO","service":"payments","msg":"Payment success: PAY-00DC3351 for 6d2f601b-155a-4a52-96c3-2a108044aff4"}
gateway-1 | 2026-06-12T20:40:56.640944621Z {"time":"2026-06-12 20:40:56,640","level":"INFO","service":"gateway","msg":"HTTP Request: POST http://payments:8082/charge "HTTP/1.1 200 OK""}
events-1 | 2026-06-12T20:40:56.646830345Z {"time":"2026-06-12 20:40:56,646","level":"INFO","service":"events","msg":"Order confirmed: 6d2f601b-155a-4a52-96c3-2a108044aff4"}
gateway-1 | 2026-06-12T20:40:56.647926470Z {"time":"2026-06-12 20:40:56,647","level":"INFO","service":"gateway","msg":"HTTP Request: POST http://events:8081/reservations/6d2f601b-155a-4a52-96c3-2a108044aff4/confirm "HTTP/1.1 200 OK""}
gateway-1 | 2026-06-12T20:40:56.648985581Z INFO: 172.28.0.1:36396 - "POST /reserve/6d2f601b-155a-4a52-96c3-2a108044aff4/pay HTTP/1.1" 200 OK
```

- `20:40:56.378` - `events` - reservation created
- `20:40:56.380` - `gateway` - reserve call to `events` returned `200`
- `20:40:56.639` - `payments` - charge completed
- `20:40:56.646` - `events` - order confirmed
- `20:40:56.648` - `gateway` - final pay response returned `200`

Total end-to-end time from the first reserve log to the final gateway pay response was about `270 ms`.