Skip to content

Comments

Add Docker support with distroless image#73

Open
leisefuxX wants to merge 2 commits intooxzi:mainfrom
leisefuxX:docker
Open

Add Docker support with distroless image#73
leisefuxX wants to merge 2 commits intooxzi:mainfrom
leisefuxX:docker

Conversation

@leisefuxX
Copy link

@leisefuxX leisefuxX commented Jan 14, 2026

Summary

This PR adds Docker containerization support using Google's distroless base image, providing a minimal and secure deployment option for gosh under linux

Changes

  1. Bug fix: Architecture-specific syscall name for fstatat/newfstatat

    • The seccomp-bpf filter failed on x86_64 because fstatat is only valid on arm64
    • Added runtime check using runtime.GOARCH to select the correct syscall name
  2. Docker support:

    • Dockerfile: Multi-stage build with distroless runtime
    • docker-compose.yml: Simple deployment configuration
    • gosh.container.yml: Template configuration
    • config/: Example configuration directory

Design Decisions

Base Image: gcr.io/distroless/static-debian12

  • Minimal attack surface (no shell, no package manager)
  • Tiny image size (~24MB total, ~6MB content)
  • Industry standard used by Kubernetes, Knative, Tekton, etc.

Privilege Model

The container leverages gosh's built-in privilege separation:

  • Container starts as root (required for chroot()/setuid() syscalls)
  • gosh drops privileges to UID 65532 after initialization
  • Seccomp-BPF filters are applied by gosh itself
  • Custom /etc/passwd and /etc/group added for user.Lookup() compatibility

Configuration

/config/gosh.yml      - main configuration (required)
/config/index.html    - custom index template (optional)
/config/favicon.ico   - favicon (optional)
/config/custom.css    - custom styles (optional)
/data/                - persistent storage

Usage

# Using docker-compose
docker compose up -d

# Or manually
docker run -d -p 8080:8080 \
  -v ./config:/config:ro \
  -v gosh-data:/data \
  gorja/gosh:0.6.0

Pre-built Image

A pre-built image is available on Docker Hub: gorja/gosh:0.6.0

Test plan

  • Build image successfully
  • Container starts without errors
  • Privilege dropping works (processes run as UID 65532)
  • Seccomp-BPF filters apply correctly on x86_64
  • File upload/download works
  • Custom index.html is served

🤖 Generated with Claude Code

leisefuxX and others added 2 commits January 14, 2026 09:47
On x86_64, the syscall is named "newfstatat" while on arm64 it's "fstatat".
The previous code always used "fstatat" which caused seccomp-bpf filter
assembly to fail on x86_64 with:
  "failed to assemble policy: found unknown syscalls for arch x86_64: fstatat"

This change adds a runtime check for GOARCH to use the correct syscall name.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add containerization support using Google's distroless base image for
minimal attack surface and small image size (~24MB).

Files added in contrib/docker/:
- Dockerfile: Multi-stage build with golang builder and distroless runtime
- docker-compose.yml: Simple deployment configuration
- gosh.container.yml: Template configuration for containerized deployment
- config/: Example configuration directory with gosh.yml and index.html

Key design decisions:

1. Base Image: gcr.io/distroless/static-debian12
   - Minimal attack surface (no shell, no package manager)
   - Small image size (~2MB base + ~6MB gosh binary)
   - Follows security best practices used by Kubernetes, Knative, etc.

2. Privilege Dropping: Leverages gosh's built-in security model
   - Container starts as root (required for chroot/setuid syscalls)
   - gosh drops privileges to UID 65532 (nonroot) after initialization
   - Seccomp-BPF filters are applied by gosh itself
   - Custom /etc/passwd and /etc/group for user.Lookup() compatibility

3. Configuration:
   - Mount /config directory with gosh.yml and optional static files
   - Mount /data volume for persistent storage
   - All paths in container config point to /config/* and /data/*

Usage (from contrib/docker/):
  docker compose up -d

Or manually (from repo root):
  docker build -f contrib/docker/Dockerfile -t gosh:distroless .
  docker run -d -p 8080:8080 \
    -v ./contrib/docker/config:/config:ro \
    -v gosh-data:/data \
    gosh:distroless

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Owner

@oxzi oxzi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First, thanks a lot for using gosh and this PR. Please excuse the delay.

In general, I would like to have a Dockerfile in here, but there are a few things needed to be addressed first. Please take a look at my comments, added after a first vibe check of this PR.

Comment on lines +95 to +96
} else {
seccompFilter = append(seccompFilter, "newfstatat")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not required, since newfstatat is already part of @system-services. Already described when introducing this change in c55f4fa.

https://github.com/oxzi/syscallset-go/blob/v0.1.7/syscalls.go#L1133-L1333

Furthermore, this binary logic assumes that newfstatat is available on every architecture that is not arm64. Unsure about that.

Comment on lines +92 to +94
// fstatat syscall name differs per architecture
if runtime.GOARCH == "arm64" {
seccompFilter = append(seccompFilter, "fstatat")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meh. Seems like I don't know my own library. I assumed that syscallset-go ignore unknown system calls, but it only does so when passing a syscall set.

I have changed this with the just released - and already bumped here - version 0.1.7.

Thus, no further changes in the Go code are required from this PR. Just rebase :)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not a huge fan of having duplicated configuration files. Changes to the main gosh.yml will be forgotten and not being reapplied here.

Thus, how about copying it into the container, do the necessary changes via sed or yq within the container build stage and still allow users to overlay/overwrite it?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quite same as https://github.com/oxzi/gosh/pull/73/changes#r2738584441. Is there even a change compared to ./index.html?

@@ -0,0 +1,45 @@
# Build stage
FROM golang:1.23-bookworm AS build
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't use an LLM (w/o Internet access) to pin "latest" versions.

Especially in this case, these versions are dangerously outdated, as Go only supports the two last major versions, being 1.24 and 1.25 at the time of writing. Since Go 1.23's EOL, there were multiple security fixes in 1.24 and 1.25, many of those might also affect 1.23.

Furthermore, Debian stable is trixie now.

EXPOSE 8080

ENTRYPOINT ["/gosh"]
CMD ["-config", "/config/gosh.yml"]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The /config directory is not mentioned as a VOLUME in the Dockerfile, but only referenced through the docker-compose.yml file.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this file for? Is it even being referenced?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants