FossilRepo

Docker Scout fixes: non-root user + supply chain attestations Non-root: gunicorn now runs as unprivileged 'app' user via gosu. Entrypoint starts as root (for sshd), then drops privileges. Data dirs owned by app user, fossil user has read access for SSH sync. Supply chain: added provenance=true and sbom=true to Docker buildx push in publish workflow for SLSA attestations.

lmata 2026-04-07 18:31 trunk
Commit 1ef5042cd533185c6ed7616cbb232bd4cafb6d2002d9207afc2ce5599af258c8
--- .github/workflows/publish.yaml
+++ .github/workflows/publish.yaml
@@ -123,5 +123,7 @@
123123
tags: |
124124
conflicthq/fossilrepo:${{ steps.version.outputs.tag }}
125125
conflicthq/fossilrepo:latest
126126
cache-from: type=gha
127127
cache-to: type=gha,mode=max
128
+ provenance: true
129
+ sbom: true
128130
--- .github/workflows/publish.yaml
+++ .github/workflows/publish.yaml
@@ -123,5 +123,7 @@
123 tags: |
124 conflicthq/fossilrepo:${{ steps.version.outputs.tag }}
125 conflicthq/fossilrepo:latest
126 cache-from: type=gha
127 cache-to: type=gha,mode=max
 
 
128
--- .github/workflows/publish.yaml
+++ .github/workflows/publish.yaml
@@ -123,5 +123,7 @@
123 tags: |
124 conflicthq/fossilrepo:${{ steps.version.outputs.tag }}
125 conflicthq/fossilrepo:latest
126 cache-from: type=gha
127 cache-to: type=gha,mode=max
128 provenance: true
129 sbom: true
130
+8
--- Dockerfile
+++ Dockerfile
@@ -58,15 +58,23 @@
5858
RUN chmod +x /usr/local/bin/fossil-shell
5959
6060
# Generate host keys if they don't exist (entrypoint will handle persistent keys)
6161
RUN ssh-keygen -A
6262
63
+# Create non-root app user for running gunicorn
64
+RUN useradd -r -m -d /home/app -s /bin/false app \
65
+ && chown -R app:app /app /data
66
+
6367
ENV PYTHONUNBUFFERED=1
6468
ENV PYTHONDONTWRITEBYTECODE=1
6569
ENV DJANGO_SETTINGS_MODULE=config.settings
6670
6771
EXPOSE 8000 2222
6872
6973
COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
7074
RUN chmod +x /usr/local/bin/entrypoint.sh
7175
76
+# Install gosu for privilege dropping in entrypoint
77
+RUN apt-get update && apt-get install -y --no-install-recommends gosu && rm -rf /var/lib/apt/lists/*
78
+
79
+# Entrypoint runs as root (to start sshd), then drops to app user for gunicorn
7280
CMD ["/usr/local/bin/entrypoint.sh"]
7381
--- Dockerfile
+++ Dockerfile
@@ -58,15 +58,23 @@
58 RUN chmod +x /usr/local/bin/fossil-shell
59
60 # Generate host keys if they don't exist (entrypoint will handle persistent keys)
61 RUN ssh-keygen -A
62
 
 
 
 
63 ENV PYTHONUNBUFFERED=1
64 ENV PYTHONDONTWRITEBYTECODE=1
65 ENV DJANGO_SETTINGS_MODULE=config.settings
66
67 EXPOSE 8000 2222
68
69 COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
70 RUN chmod +x /usr/local/bin/entrypoint.sh
71
 
 
 
 
72 CMD ["/usr/local/bin/entrypoint.sh"]
73
--- Dockerfile
+++ Dockerfile
@@ -58,15 +58,23 @@
58 RUN chmod +x /usr/local/bin/fossil-shell
59
60 # Generate host keys if they don't exist (entrypoint will handle persistent keys)
61 RUN ssh-keygen -A
62
63 # Create non-root app user for running gunicorn
64 RUN useradd -r -m -d /home/app -s /bin/false app \
65 && chown -R app:app /app /data
66
67 ENV PYTHONUNBUFFERED=1
68 ENV PYTHONDONTWRITEBYTECODE=1
69 ENV DJANGO_SETTINGS_MODULE=config.settings
70
71 EXPOSE 8000 2222
72
73 COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
74 RUN chmod +x /usr/local/bin/entrypoint.sh
75
76 # Install gosu for privilege dropping in entrypoint
77 RUN apt-get update && apt-get install -y --no-install-recommends gosu && rm -rf /var/lib/apt/lists/*
78
79 # Entrypoint runs as root (to start sshd), then drops to app user for gunicorn
80 CMD ["/usr/local/bin/entrypoint.sh"]
81
--- docker/entrypoint.sh
+++ docker/entrypoint.sh
@@ -1,28 +1,28 @@
11
#!/bin/bash
2
-# fossilrepo entrypoint — starts sshd + gunicorn.
2
+# fossilrepo entrypoint — starts sshd as root, drops to app user for gunicorn.
33
#
4
-# sshd runs in the background for Fossil SSH access.
5
-# gunicorn runs in the foreground as the main process.
4
+# sshd needs root for port binding and key access.
5
+# gunicorn runs as the unprivileged 'app' user.
66
77
set -euo pipefail
88
99
# Ensure SSH host keys exist (persistent across restarts via volume)
1010
if [ ! -f /etc/ssh/ssh_host_ed25519_key ]; then
1111
ssh-keygen -A
1212
fi
1313
14
-# Ensure SSH data dir exists and has correct permissions
15
-mkdir -p /data/ssh
14
+# Ensure data dirs exist with correct permissions
15
+mkdir -p /data/ssh /data/repos /data/trash
1616
touch /data/ssh/authorized_keys
1717
chmod 600 /data/ssh/authorized_keys
1818
chown -R fossil:fossil /data/ssh
19
-
20
-# Ensure fossil user can read repos
21
-chown -R fossil:fossil /data/repos
19
+chown -R app:app /data/repos /data/trash
20
+# fossil user needs read access to repos for SSH sync
21
+chmod -R g+r /data/repos
2222
23
-# Start sshd in the background (non-detach mode with -D would block)
23
+# Start sshd in the background (runs as root)
2424
/usr/sbin/sshd -p 2222 -e &
2525
SSHD_PID=$!
2626
echo "sshd started (PID $SSHD_PID) on port 2222"
2727
2828
# Trap signals to clean up sshd
@@ -31,10 +31,10 @@
3131
kill "$SSHD_PID" 2>/dev/null || true
3232
wait "$SSHD_PID" 2>/dev/null || true
3333
}
3434
trap cleanup EXIT TERM INT
3535
36
-# Run gunicorn in the foreground
37
-exec gunicorn config.wsgi:application \
36
+# Drop to non-root 'app' user for gunicorn
37
+exec gosu app gunicorn config.wsgi:application \
3838
--bind 0.0.0.0:8000 \
3939
--workers 3 \
4040
--timeout 120
4141
--- docker/entrypoint.sh
+++ docker/entrypoint.sh
@@ -1,28 +1,28 @@
1 #!/bin/bash
2 # fossilrepo entrypoint — starts sshd + gunicorn.
3 #
4 # sshd runs in the background for Fossil SSH access.
5 # gunicorn runs in the foreground as the main process.
6
7 set -euo pipefail
8
9 # Ensure SSH host keys exist (persistent across restarts via volume)
10 if [ ! -f /etc/ssh/ssh_host_ed25519_key ]; then
11 ssh-keygen -A
12 fi
13
14 # Ensure SSH data dir exists and has correct permissions
15 mkdir -p /data/ssh
16 touch /data/ssh/authorized_keys
17 chmod 600 /data/ssh/authorized_keys
18 chown -R fossil:fossil /data/ssh
19
20 # Ensure fossil user can read repos
21 chown -R fossil:fossil /data/repos
22
23 # Start sshd in the background (non-detach mode with -D would block)
24 /usr/sbin/sshd -p 2222 -e &
25 SSHD_PID=$!
26 echo "sshd started (PID $SSHD_PID) on port 2222"
27
28 # Trap signals to clean up sshd
@@ -31,10 +31,10 @@
31 kill "$SSHD_PID" 2>/dev/null || true
32 wait "$SSHD_PID" 2>/dev/null || true
33 }
34 trap cleanup EXIT TERM INT
35
36 # Run gunicorn in the foreground
37 exec gunicorn config.wsgi:application \
38 --bind 0.0.0.0:8000 \
39 --workers 3 \
40 --timeout 120
41
--- docker/entrypoint.sh
+++ docker/entrypoint.sh
@@ -1,28 +1,28 @@
1 #!/bin/bash
2 # fossilrepo entrypoint — starts sshd as root, drops to app user for gunicorn.
3 #
4 # sshd needs root for port binding and key access.
5 # gunicorn runs as the unprivileged 'app' user.
6
7 set -euo pipefail
8
9 # Ensure SSH host keys exist (persistent across restarts via volume)
10 if [ ! -f /etc/ssh/ssh_host_ed25519_key ]; then
11 ssh-keygen -A
12 fi
13
14 # Ensure data dirs exist with correct permissions
15 mkdir -p /data/ssh /data/repos /data/trash
16 touch /data/ssh/authorized_keys
17 chmod 600 /data/ssh/authorized_keys
18 chown -R fossil:fossil /data/ssh
19 chown -R app:app /data/repos /data/trash
20 # fossil user needs read access to repos for SSH sync
21 chmod -R g+r /data/repos
22
23 # Start sshd in the background (runs as root)
24 /usr/sbin/sshd -p 2222 -e &
25 SSHD_PID=$!
26 echo "sshd started (PID $SSHD_PID) on port 2222"
27
28 # Trap signals to clean up sshd
@@ -31,10 +31,10 @@
31 kill "$SSHD_PID" 2>/dev/null || true
32 wait "$SSHD_PID" 2>/dev/null || true
33 }
34 trap cleanup EXIT TERM INT
35
36 # Drop to non-root 'app' user for gunicorn
37 exec gosu app gunicorn config.wsgi:application \
38 --bind 0.0.0.0:8000 \
39 --workers 3 \
40 --timeout 120
41

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button