Skip to content
Merged
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
Binary file added .DS_Store
Binary file not shown.
2 changes: 1 addition & 1 deletion .github/workflows/layerlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.26.3'
go-version: '1.26.4'

- name: Build LayerLint
run: go build -o layerlint ./cmd/layerlint
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.26.3'
go-version: '1.26.4'

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
Expand Down
35 changes: 35 additions & 0 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Backend Verify

on:
pull_request:
paths:
- "**.go"
- "go.mod"
- "go.sum"
- ".github/workflows/verify.yml"
push:
branches:
- main
paths:
- "**.go"
- "go.mod"
- "go.sum"
- ".github/workflows/verify.yml"

jobs:
test-backend:
runs-on: ubuntu-latest
name: Run backend test cases

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.26.5"
cache: true

- name: Run Go tests
run: go test ./...
5 changes: 4 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM golang:1.26.3-alpine
FROM golang:1.26.5-alpine

WORKDIR /app

Expand All @@ -12,4 +12,7 @@ RUN --mount=type=cache,target=/go/pkg/mod \
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1

ENTRYPOINT ["/app/layerlint"]
42 changes: 42 additions & 0 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,3 +236,45 @@ RUN curl -L https://example.com/binary -o /usr/local/bin/tool && \
```

Get the checksum from the official source, verify it.

## missing-healthcheck

**Medium Severity**

Production containers should tell orchestrators if they're healthy. Without `HEALTHCHECK`, Kubernetes and Docker can't tell if your app is actually serving traffic or just running but broken.

Bad:
```dockerfile
FROM node:18
CMD ["node", "server.js"] # No way to check if it's working
```

Good:
```dockerfile
FROM node:18
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
CMD ["node", "server.js"]
```

Add a health endpoint to your app and check it. Makes rollouts safer, debugging easier.

## apt-update-without-install (enhanced)

**Medium Severity**

Don't run `apt-get update` in a separate RUN from `apt-get install`. The update layer gets cached and goes stale.

Also: always use `--no-install-recommends` with `apt-get install`. It skips recommended packages that are usually unnecessary — smaller image, smaller attack surface.

Bad:
```dockerfile
RUN apt-get update && apt-get install -y curl
```

Good:
```dockerfile
RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*
```

Chain them together, use `--no-install-recommends`, clean up after.
50 changes: 41 additions & 9 deletions frontend/src/components/RulesShowcase.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,48 @@ const RulesShowcase = () => {
severity: 'Critical',
title: 'Broad COPY Before Dependencies',
description: 'Copying all source files before installing dependencies breaks layer cache and forces unnecessary rebuilds.',
impact: '30-45s slower builds',
impact: '30-45s slower builds per change',
fix: 'Copy dependency manifests first, install dependencies, then copy source code'
},
{
id: 'dockerfile/inefficient-cache',
id: 'dockerfile/missing-healthcheck',
severity: 'High',
title: 'Inefficient Layer Ordering',
description: 'Frequently changing instructions placed before stable ones invalidate cache unnecessarily.',
impact: '15-20s slower builds',
fix: 'Place static dependencies and configuration before frequently changing source code'
title: 'Missing HEALTHCHECK Instruction',
description: 'Production containers need HEALTHCHECK so orchestrators can detect if the app is actually healthy, not just running.',
impact: 'Unreliable rollouts, no auto-healing',
fix: 'Add HEALTHCHECK with curl/wget against a health endpoint before CMD/ENTRYPOINT'
},
{
id: 'dockerfile/run-as-root',
severity: 'High',
title: 'Container Runs as Root',
description: 'Running as root gives processes full system privileges, violating the principle of least privilege.',
impact: 'Security risk, compliance failures',
fix: 'Create non-root user, use USER instruction before CMD'
},
{
id: 'dockerfile/copying-secrets',
severity: 'High',
title: 'Secrets Copied Into Image',
description: 'Secrets copied into images remain in layer history even if deleted later.',
impact: 'Credential leaks, security breaches',
fix: 'Use BuildKit --mount=type=secret instead of COPY for sensitive files'
},
{
id: 'dockerfile/apt-no-recommends',
severity: 'Medium',
title: 'apt-get Without --no-install-recommends',
description: 'Installing packages without --no-install-recommends pulls in unnecessary dependencies, bloating the image.',
impact: '50-200MB larger images, increased attack surface',
fix: 'Add --no-install-recommends to all apt-get install commands'
},
{
id: 'dockerfile/wget-curl-checksum',
severity: 'Medium',
title: 'Downloads Without Checksum Verification',
description: 'Downloading files without verifying checksums opens the door to supply chain attacks.',
impact: 'Supply chain attacks, corrupted binaries',
fix: 'Verify all downloads with sha256sum'
},
{
id: 'dockerfile/missing-cache-mounts',
Expand Down Expand Up @@ -53,7 +85,7 @@ const RulesShowcase = () => {
Comprehensive Coverage
</h2>
<p className="text-xl text-gray-600 max-w-2xl mx-auto">
12+ intelligent rules covering all Docker caching anti-patterns
14+ intelligent rules covering Docker caching, security, and production-readiness anti-patterns
</p>
</motion.div>

Expand Down Expand Up @@ -83,7 +115,7 @@ const RulesShowcase = () => {
</div>
</div>
</div>

<div className="mt-6 pt-6 border-t border-gray-200">
<span className="text-sm text-gray-500">Impact: {rule.impact}</span>
</div>
Expand All @@ -95,4 +127,4 @@ const RulesShowcase = () => {
)
}

export default RulesShowcase
export default RulesShowcase
52 changes: 34 additions & 18 deletions frontend/src/pages/Docs.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const rules = [
category: 'Layer Caching',
description: 'Copying application code before installing dependencies breaks Docker layer caching. When you change any file in your app, Docker rebuilds all layers including dependency installation.',
problem: 'Every code change forces complete dependency reinstallation, even if package.json/requirements.txt/go.mod hasn\'t changed. This multiplies build times by 10-50x for large projects.',
impact: '45 seconds wasted per build × 50 builds/day = 37.5 minutes of developer time lost daily',
impact: '45 seconds wasted per build x 50 builds/day = 37.5 minutes of developer time lost daily',
badExample: `FROM node:20-alpine

COPY . /app
Expand Down Expand Up @@ -138,13 +138,13 @@ Context: 85 MB transferred, 2s upload time`,
badExample: `RUN npm install
RUN pip install -r requirements.txt
RUN go mod download`,
goodExample: `RUN --mount=type=cache,target=/root/.npm \\
goodExample: `RUN --mount=type=cache,target=/root/.npm \
npm install

RUN --mount=type=cache,target=/root/.cache/pip \\
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt

RUN --mount=type=cache,target=/go/pkg/mod \\
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download`,
fix: 'Add --mount=type=cache,target=<cache-dir> to RUN commands that download packages. Use correct cache paths for each package manager.'
},
Expand All @@ -154,15 +154,15 @@ RUN --mount=type=cache,target=/go/pkg/mod \\
severity: 'medium',
category: 'Layer Caching',
description: 'Running apt-get update in a separate layer from apt-get install causes caching issues. The update layer is cached, but package lists become stale.',
problem: 'Cached update layers mean subsequent builds use outdated package lists, potentially installing vulnerable or incorrect versions.',
impact: 'Stale package installations, potential security vulnerabilities, build failures',
problem: 'Cached update layers mean subsequent builds use outdated package lists, potentially installing vulnerable or incorrect versions. Also, omitting --no-install-recommends installs unnecessary packages, bloating the image and increasing attack surface.',
impact: 'Stale package installations, potential security vulnerabilities, build failures, 50-200MB larger images',
badExample: `RUN apt-get update
RUN apt-get install -y curl wget`,
goodExample: `RUN apt-get update && apt-get install -y \\
curl \\
wget \\
goodExample: `RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
wget \
&& rm -rf /var/lib/apt/lists/*`,
fix: 'Chain apt-get update && apt-get install in single RUN. Clean lists after install with rm -rf /var/lib/apt/lists/* to reduce image size.'
fix: 'Chain apt-get update && apt-get install in single RUN. Always use --no-install-recommends to skip unnecessary packages. Clean lists after install with rm -rf /var/lib/apt/lists/* to reduce image size.'
},
{
id: 'multiple-broad-copies',
Expand Down Expand Up @@ -214,6 +214,22 @@ RUN npm ci --only=production
COPY --from=builder /app/dist ./dist`,
fix: 'Install dependencies once per stage. Use npm ci for production. In multi-stage builds, consider copying node_modules from builder if appropriate.'
},
{
id: 'missing-healthcheck',
title: 'Missing HEALTHCHECK Instruction',
severity: 'medium',
category: 'Production Readiness',
description: 'Production containers should tell orchestrators if they are healthy. Without HEALTHCHECK, Kubernetes and Docker cannot distinguish between a running process and a healthy, traffic-serving application.',
problem: 'Without HEALTHCHECK, container orchestrators have no way to know if your app is actually working. A stalled or deadlocked process appears "running" but serves no traffic. Rollouts become risky, auto-healing is impossible.',
impact: 'Unreliable rollouts, no auto-healing, silent failures in production, longer incident response times',
badExample: `FROM node:20-alpine
CMD ["node", "server.js"]`,
goodExample: `FROM node:20-alpine
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
CMD ["node", "server.js"]`,
fix: 'Add a health endpoint to your application and configure HEALTHCHECK with appropriate intervals. Use --start-period to account for startup time. Keep the check lightweight and specific to actual app health, not just process existence.'
},
{
id: 'add-instead-of-copy',
title: 'ADD Instead of COPY',
Expand Down Expand Up @@ -243,11 +259,11 @@ COPY . /app/`,
impact: 'Security vulnerabilities, supply chain attacks, corrupted binaries in production',
badExample: `RUN curl -L https://example.com/binary -o /usr/local/bin/tool
RUN wget https://example.com/package.tar.gz && tar -xzf package.tar.gz`,
goodExample: `RUN curl -L https://example.com/binary -o /usr/local/bin/tool \\
goodExample: `RUN curl -L https://example.com/binary -o /usr/local/bin/tool \
&& echo "a3b5c7d9e1f2... /usr/local/bin/tool" | sha256sum -c -

RUN wget https://example.com/package.tar.gz \\
&& echo "f2e1d9c7b5a3... package.tar.gz" | sha256sum -c - \\
RUN wget https://example.com/package.tar.gz \
&& echo "f2e1d9c7b5a3... package.tar.gz" | sha256sum -c - \
&& tar -xzf package.tar.gz`,
fix: 'Always verify downloads with sha256sum or sha512sum. Get checksums from official sources. Use && echo "CHECKSUM filename" | sha256sum -c - pattern.'
}
Expand Down Expand Up @@ -316,10 +332,10 @@ function Docs() {
to="/"
className="text-sm text-gray-600 hover:text-gray-900 transition-colors"
>
Back to Home
&larr; Back to Home
</Link>
<a
href="https://github.com/yourusername/layerlint"
href="https://github.com/vviveksharma/layerLint"
target="_blank"
rel="noopener noreferrer"
className="p-2 rounded-lg hover:bg-gray-100 transition-colors"
Expand Down Expand Up @@ -451,11 +467,11 @@ function Docs() {
<h2 className="text-2xl font-serif font-bold text-gray-900 mb-4">
Code Examples
</h2>

<div className="grid md:grid-cols-2 gap-6">
<div>
<div className="flex items-center gap-2 mb-3">
<span className="text-sm font-semibold text-red-600"> Bad Practice</span>
<span className="text-sm font-semibold text-red-600">&times; Bad Practice</span>
</div>
<pre className="bg-red-50 border border-red-200 rounded-lg p-4 overflow-x-auto text-sm">
<code className="text-red-900 font-mono">{selectedRule.badExample}</code>
Expand All @@ -464,7 +480,7 @@ function Docs() {

<div>
<div className="flex items-center gap-2 mb-3">
<span className="text-sm font-semibold text-green-600"> Good Practice</span>
<span className="text-sm font-semibold text-green-600">&#10003; Good Practice</span>
</div>
<pre className="bg-green-50 border border-green-200 rounded-lg p-4 overflow-x-auto text-sm">
<code className="text-green-900 font-mono">{selectedRule.goodExample}</code>
Expand Down
7 changes: 4 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
module github.com/vviveksharma/layerLint

go 1.26.3
go 1.26.5

require (
github.com/moby/buildkit v0.30.0
github.com/spf13/cobra v1.10.2
)

require (
github.com/containerd/typeurl/v2 v2.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/containerd/typeurl/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/spf13/pflag v1.0.10 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)
Loading
Loading