How It Works Features Pricing Blog Error Guides
Log In Start Free Trial
Echo · Go

Fix FileServeError: open static/assets/logo.png: no such file or directory in Echo

This error occurs when Echo's static file handler cannot find the requested file at the specified filesystem path, typically because the working directory differs between development and production or the static file path is relative. Fix it by using embed.FS for production builds or configuring an absolute path to the static directory.

Reading the Stack Trace

2024/03/15 16:00:30 echo: GET /static/logo.png -> echo.(*Echo).Static.func1 | 404 | 0.104ms | 127.0.0.1 goroutine 34 [running]: runtime/debug.Stack() /usr/local/go/src/runtime/debug/stack.go:24 +0x5e os.(*File).openFile(0x0, {0x1028f1e60, 0x1a}, 0x0, 0x0) /usr/local/go/src/os/file.go:331 +0x148 os.Open({0x1028f1e60, 0x1a}) /usr/local/go/src/os/file.go:309 +0x48 github.com/labstack/echo/v4.(*Echo).Static.func1({0x1029e4f80, 0x14000226000}) /go/pkg/mod/github.com/labstack/echo/v4@v4.11.4/echo.go:558 +0x134 github.com/labstack/echo/v4.(*Echo).ServeHTTP(0x14000128680, {0x1029e4f80, 0x140001c40e0}, 0x140002b4000) /go/pkg/mod/github.com/labstack/echo/v4@v4.11.4/echo.go:669 +0x1a0

Here's what each line means:

Common Causes

1. Relative path changes with working directory

Using a relative path for the static directory works in development but fails when the binary runs from a different directory.

func main() {
	e := echo.New()
	e.Static("/static", "static/assets") // relative path
	e.Start(":8080")
}

2. Static files not included in Docker image

The Dockerfile copies the binary but not the static directory, so files are missing at runtime.

# Dockerfile
FROM golang:1.22 AS builder
COPY . .
RUN go build -o app

FROM alpine
COPY --from=builder /app/app /app
# Missing: COPY --from=builder /app/static /static
CMD ["/app"]

3. Path traversal vulnerability

The static handler does not sanitize the requested path, allowing ../../../etc/passwd attacks.

e.Static("/files", "./uploads") // no path sanitization

The Fix

Use Go's embed package to bundle static files into the binary. This eliminates path resolution issues across environments and ensures files are always available regardless of the working directory or container layout.

Before (broken)
func main() {
	e := echo.New()
	e.Static("/static", "static/assets")
	e.Start(":8080")
}
After (fixed)
//go:embed static/assets/*
var staticFS embed.FS

func main() {
	e := echo.New()

	contentFS, err := fs.Sub(staticFS, "static/assets")
	if err != nil {
		log.Fatal(err)
	}
	assetHandler := http.FileServer(http.FS(contentFS))
	e.GET("/static/*", echo.WrapHandler(http.StripPrefix("/static/", assetHandler)))

	e.Start(":8080")
}

Testing the Fix

package main_test

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/labstack/echo/v4"
	"github.com/stretchr/testify/assert"
)

func TestStaticFile_Exists(t *testing.T) {
	e := setupEchoWithStatic()

	req := httptest.NewRequest(http.MethodGet, "/static/logo.png", nil)
	rec := httptest.NewRecorder()
	e.ServeHTTP(rec, req)

	assert.Equal(t, http.StatusOK, rec.Code)
}

func TestStaticFile_NotFound(t *testing.T) {
	e := setupEchoWithStatic()

	req := httptest.NewRequest(http.MethodGet, "/static/nonexistent.png", nil)
	rec := httptest.NewRecorder()
	e.ServeHTTP(rec, req)

	assert.Equal(t, http.StatusNotFound, rec.Code)
}

Run your tests:

go test ./... -v

Pushing Through CI/CD

git checkout -b fix/echo-file-serve-error,git add main.go,git commit -m "fix: embed static assets to avoid file path resolution issues",git push origin fix/echo-file-serve-error

Your CI config should look something like this:

name: CI
on:
  pull_request:
    branches: [main]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: '1.22'
      - run: go mod download
      - run: go vet ./...
      - run: go test ./... -race -coverprofile=coverage.out
      - run: go build ./...

The Full Manual Process: 18 Steps

Here's every step you just went through to fix this one bug:

  1. Notice the error alert or see it in your monitoring tool
  2. Open the error dashboard and read the stack trace
  3. Identify the file and line number from the stack trace
  4. Open your IDE and navigate to the file
  5. Read the surrounding code to understand context
  6. Reproduce the error locally
  7. Identify the root cause
  8. Write the fix
  9. Run the test suite locally
  10. Fix any failing tests
  11. Write new tests covering the edge case
  12. Run the full test suite again
  13. Create a new git branch
  14. Commit and push your changes
  15. Open a pull request
  16. Wait for code review
  17. Merge and deploy to production
  18. Monitor production to confirm the error is resolved

Total time: 30-60 minutes. For one bug.

Or Let bugstack Fix It in Under 2 minutes

Every step above? bugstack does it automatically.

Step 1: Install the SDK

go get github.com/bugstack/sdk

Step 2: Initialize

import "github.com/bugstack/sdk"

func init() {
  bugstack.Init(os.Getenv("BUGSTACK_API_KEY"))
}

Step 3: There is no step 3.

bugstack handles everything from here:

  1. Captures the stack trace and request context
  2. Pulls the relevant source files from your GitHub repo
  3. Analyzes the error and understands the code context
  4. Generates a minimal, verified fix
  5. Runs your existing test suite
  6. Pushes through your CI/CD pipeline
  7. Deploys to production (or opens a PR for review)

Time from error to fix deployed: Under 2 minutes.

Human involvement: zero.

Try bugstack Free →

No credit card. 5-minute setup. Cancel anytime.

Deploying the Fix (Manual Path)

  1. Run go test ./... locally to confirm static files are served.
  2. Open a pull request with the embed changes.
  3. Wait for CI checks to pass on the PR.
  4. Have a teammate review and approve the PR.
  5. Merge to main and verify static assets load in staging.

Frequently Asked Questions

BugStack verifies that embedded files are accessible, runs tests for both existing and non-existent paths, and checks that no path traversal is possible before marking it safe to deploy.

BugStack never pushes directly to production. Every fix goes through a pull request with full CI checks, so your team can review it before merging.

Yes. All embedded files are compiled into the binary. For large asset directories, consider serving from a CDN or object storage instead.

Wrap the file server handler with middleware that sets Cache-Control headers. For hashed filenames, use a long max-age. For others, use ETag-based validation.