When GO_PROD=1 is set at runtime, go build receives -trimpath and
-ldflags="-s -w" to strip source paths, symbol tables, and DWARF debug
info from compiled binaries. Applied to go build only — go test is
unaffected so stack traces remain readable during development.
Usage: docker run --env GO_PROD=1 --rm -v "$(pwd)":/app casjaysdev/go
- rootfs/usr/local/bin/go-workflow: add BUILD_FLAGS array populated when
GO_PROD=1; pass flags to go build step only; print mode notice when active
rootfs/usr/local/bin/go-workflow
- Dockerfile: add GOFLAGS=-buildvcs=false to prevent VCS stamp errors when
users mount projects without a .git directory (Go 1.18+ default behaviour)
- Dockerfile: add GOTELEMETRY=off to disable Go 1.23+ telemetry in containers
- Dockerfile: add GOPROXY=https://proxy.golang.org,direct (explicit default,
makes override via --env obvious)
- Dockerfile: add GOBIN=/usr/local/bin to the final stage — was set in the
build stage but missing from the scratch-based final stage; without it,
go install inside the running container lands binaries in /usr/local/share/go/bin
instead of /usr/local/bin
Dockerfile
go install rejects multiple packages from different modules in a single
invocation. Split all 11 go install calls into individual commands chained
with &&.
- Dockerfile: split single multi-package go install into 11 separate
go install calls, one per module, chained with &&
Dockerfile
The arm64 build was stuck for 12+ hours compiling gopls, dlv, and other
Go tools from source under QEMU user-mode emulation. QEMU makes Go
compilation 20-50× slower than native; gopls alone can take hours.
Fix: add a --platform=$BUILDPLATFORM go-tools stage in the Dockerfile that
cross-compiles all go install tools natively on amd64 using Go's built-in
cross-compilation (GOOS=linux GOARCH=$TARGETARCH). The binaries are then
COPYed into the main build stage before 05-custom.sh runs. No QEMU is
involved for any compilation step.
- Dockerfile: add go-tools stage using --platform=$BUILDPLATFORM with golang:alpine;
cross-compiles goimports, stringer, gopls, govulncheck, dlv, gops, benchstat,
wire, mockgen, protoc-gen-go, protoc-gen-go-grpc; COPY /go/bin/ → /usr/local/bin/
before the 05-custom.sh RUN layer
- rootfs/root/docker/setup/05-custom.sh: remove all go install commands (11 tools
now provided by the Dockerfile stage); keep go clean -modcache/-cache cleanup
Dockerfile
rootfs/root/docker/setup/05-custom.sh
The casjaysdev/alpine base image routes *.github.com IPv6 addresses through
casjay.in infrastructure, which presents CN=casjay.in instead of GitHub's
certificate. curl -fsSL to api.github.com resolves to 2402:d0c0:12:e04e::1
and fails with "no alternative certificate subject name matches target
hostname api.github.com". Standard alpine:latest uses the IPv4 address
140.82.114.6 and succeeds.
Fix: write "-4" to /root/.curlrc at the top of 05-custom.sh so every curl
call in the script (including the golangci-lint installer sub-script) uses
IPv4. Build now completes successfully.
- rootfs/root/docker/setup/05-custom.sh: add printf '-4' > /root/.curlrc
before any network calls
rootfs/root/docker/setup/05-custom.sh
set -e in go-workflow made the retVal/GO_EXITCODE accumulator dead code —
any failing run_step call exits the script immediately via set -e before
retVal=$? is ever reached, so GO_EXITCODE only ever sums zeros.
Remove the accumulator and simplify run_step; set -e already propagates
failures naturally.
__exec_command in the *) and exec) cases only used the first element of its
arg array (local cmdExec="${arg:-}" expands to arg[0]), silently dropping
every subsequent word. "docker run casjaysdev/go go test ./..." ran just
"go" with no subcommand. Replace __exec_command with exec "$@" directly in
both cases; exec is correct (no extra process) and preserves all args.
00-go.sh exported CONTAINER_INIT and SERVICE_USES_PID inside a subshell
( source "$init" ) — those exports never propagated back to the parent
entrypoint. Move SERVICE_USES_PID="no" to go.sh (sourced by the parent
shell before __start_init_scripts runs) so the framework takes the explicit
"config service, no PID" path. Rewrite 00-go.sh as a documented placeholder
explaining why the file must still exist (init_count == 0 triggers an
infinite background keep-alive loop).
- rootfs/usr/local/bin/go-workflow: remove GO_EXITCODE/retVal accumulator;
simplify run_step to drop exitCode capture (always 0 with set -e)
- rootfs/usr/local/bin/entrypoint.sh: *) case uses exec "$@" / exec go-workflow;
exec) case uses exec "$@" with proper empty-arg error instead of broken
__exec_command fallback
- rootfs/usr/local/etc/docker/env/go.sh: add SERVICE_USES_PID="no"
- rootfs/usr/local/etc/docker/init.d/00-go.sh: remove dead exports; add
comment explaining placeholder purpose
rootfs/usr/local/bin/entrypoint.sh
rootfs/usr/local/bin/go-workflow
rootfs/usr/local/etc/docker/env/go.sh
rootfs/usr/local/etc/docker/init.d/00-go.sh
- CONTAINER_NAME was hardcoded to "alpine" instead of "go"; the startup
message said "alpine" for the same reason
- HEALTH_ENABLED was hard-assigned to "yes" after env files were sourced,
silently overriding the HEALTH_ENABLED="no" set in env/go.sh; changed
to ${HEALTH_ENABLED:-yes} so the env file value is respected
- go-workflow used retval (lowercase) to capture the go vet exit code but
then read retVal (uppercase) in the accumulator; the vet failure was
silently dropped and the gofmt exit code was summed twice instead
- rootfs/usr/local/bin/entrypoint.sh: fix CONTAINER_NAME default, startup
message label, and HEALTH_ENABLED hard-override
- rootfs/usr/local/bin/go-workflow: fix retval/retVal case mismatch so
go vet exit code is correctly propagated
rootfs/usr/local/bin/entrypoint.sh
rootfs/usr/local/bin/go-workflow
- `go-workflow`: accumulate step exit codes into `GO_EXITCODE` and `exit $GO_EXITCODE` so failures propagate to the caller
- `run_step`: capture and return each step's exit code instead of silently swallowing it
- `entrypoint.sh`: replace `__exec_command go-workflow` with direct `go-workflow "$@"` so the process and args are passed correctly
- Strip trailing whitespace from `@@Other` and `@@Resource` header fields
rootfs/usr/local/bin/entrypoint.sh
rootfs/usr/local/bin/go-workflow
--domainname on the container sets the kernel domainname, which c-ares
uses to infer a search domain even when /etc/resolv.conf has no search
line. This caused c-ares to query github.com.casjay.work AAAA and get
the host's own IPv6 address, routing all outbound HTTPS to the local
nginx instead of the real server.
Adding 'search .' and 'options ndots:0' explicitly disables search
domain inference regardless of the kernel domainname setting.
- rootfs/usr/local/etc/resolv.conf: add search . and options ndots:0
rootfs/usr/local/etc/resolv.conf
Hosts with a search domain cause containers to inherit it. When the
zone has a wildcard AAAA record, public hostnames resolve to the host's
own IPv6 address instead of the real server, breaking all outbound
HTTPS and DNS from inside the container.
The entrypoint already has a hook: if /usr/local/etc/resolv.conf
exists it replaces /etc/resolv.conf at container startup. Ship a
clean resolv.conf with Cloudflare + Google DNS and no search domain
so container DNS is always correct regardless of host configuration.
- rootfs/usr/local/etc/resolv.conf: new file — clean DNS, no search domain
rootfs/usr/local/etc/resolv.conf
Workflow was incorrectly placed under .github/workflows/. Moved to the
correct Gitea location at .gitea/workflows/.
- .github/workflows/build.yml: deleted
- .gitea/workflows/build.yml: moved from .github/workflows/build.yml
.gitea/workflows/build.yml
.github/workflows/build.yml
Builds linux/amd64 + linux/arm64 in a single pass and pushes to all
configured registries simultaneously:
- Always pushes to the Gitea built-in registry using the auto-provided
GITEA_TOKEN — no secrets setup required
- Optionally pushes to Docker Hub when vars.DOCKER_USERNAME is set;
image namespace uses vars.DOCKER_ORG when set, falls back to
vars.DOCKER_USERNAME; secrets.DOCKER_PASSWORD never touches a shell
- IMAGE_TAG env var overrides the named tag, defaults to latest
- Date tag (YYMM) pushed first, named tag pushed last so latest is the
displayed pull suggestion
- All OCI annotations declared explicitly in the workflow (multi-arch
manifest lists do not carry Dockerfile LABEL values)
- All third-party Actions pinned to full commit SHAs
- .github/workflows/build.yml: new — multi-platform build and push workflow
.github/
Pre-set service-discovery vars in a Docker env file so the entrypoint's
${VAR:-$(function)} expansions skip all seven __find_* subprocess forks on
every container start. Also suppress the startup banner and health loop
since this image has no long-running daemon.
Fix overly-broad `env/` gitignore pattern that silently swallowed
rootfs/…/docker/env/ and any other env directory in the tree. Replace with
the root-anchored `/env/` and `src/env/` which only match the Python venv
paths originally intended.
- rootfs/usr/local/etc/docker/env/go.sh: new — pre-sets PHP_INI_DIR,
PHP_BIN_DIR, HTTPD_CONFIG_FILE, NGINX_CONFIG_FILE, MYSQL_CONFIG_FILE,
PGSQL_CONFIG_FILE, MONGODB_CONFIG_FILE to "none"; sets
ENTRYPOINT_MESSAGE=no and HEALTH_ENABLED=no
- .gitignore: replace bare `env/` with `/env/` and `src/env/` to avoid
matching Docker env config directories inside rootfs
.gitignore
rootfs/usr/local/etc/docker/env/
Synced from casjay-dotfiles templates. Updated functions now check
for existence before copying template-files directories, skipping
gracefully when they are absent.
- rootfs/usr/local/etc/docker/functions/entrypoint.sh: updated to latest
rootfs/usr/local/etc/docker/functions/entrypoint.sh
This is a toolchain image with no daemon or service — the generic
service env examples (mariadb, redis, certbot, etc.) and placeholder
gitkeeps have no purpose here.
- rootfs/usr/local/share/template-files/: removed entirely (24 files)
rootfs/usr/local/share/template-files/config/env/default.sample
rootfs/usr/local/share/template-files/config/env/examples/00-directory.sh
rootfs/usr/local/share/template-files/config/env/examples/addresses.sh
rootfs/usr/local/share/template-files/config/env/examples/certbot.sh
rootfs/usr/local/share/template-files/config/env/examples/couchdb.sh
rootfs/usr/local/share/template-files/config/env/examples/dockerd.sh
rootfs/usr/local/share/template-files/config/env/examples/global.sh
rootfs/usr/local/share/template-files/config/env/examples/healthcheck.sh
rootfs/usr/local/share/template-files/config/env/examples/mariadb.sh
rootfs/usr/local/share/template-files/config/env/examples/mongodb.sh
rootfs/usr/local/share/template-files/config/env/examples/networking.sh
rootfs/usr/local/share/template-files/config/env/examples/other.sh
rootfs/usr/local/share/template-files/config/env/examples/php.sh
rootfs/usr/local/share/template-files/config/env/examples/postgres.sh
rootfs/usr/local/share/template-files/config/env/examples/redis.sh
rootfs/usr/local/share/template-files/config/env/examples/services.sh
rootfs/usr/local/share/template-files/config/env/examples/ssl.sh
rootfs/usr/local/share/template-files/config/env/examples/supabase.sh
rootfs/usr/local/share/template-files/config/env/examples/webservers.sh
rootfs/usr/local/share/template-files/config/env/examples/zz-entrypoint.sh
rootfs/usr/local/share/template-files/config/.gitkeep
rootfs/usr/local/share/template-files/data/.gitkeep
rootfs/usr/local/share/template-files/defaults/.gitkeep
- .claude/settings.json: permission allowlist for docker buildx/ps/run/pull,
make check/test/build, curl fetches, and the project gitcommit invocation;
reduces per-command permission prompts during development
- .gitignore: expand Claude Code ignore patterns to cover all runtime and
personal files (backups/, cache/, file-history/, history.jsonl, projects/,
statsFile, *.lock) per project_files.md rules; settings.json is a
committable project config and remains tracked
.claude/
.gitignore
Four asset naming bugs found and fixed; gopls and govulncheck moved back
to go install (neither publishes binary release assets).
- rootfs/root/docker/setup/05-custom.sh:
- gofumpt: asset includes version tag (gofumpt_v0.x.y_linux_amd64)
- gotestsum: uses amd64/arm64 convention, not x86_64
- air: asset includes version without v prefix (air_1.x.y_linux_amd64)
- gopls: no binary release assets → go install golang.org/x/tools/gopls@latest
- govulncheck: no binary release assets → go install golang.org/x/vuln/cmd/govulncheck@latest
- _gh_latest: add null/empty guard; print per-tool progress; auth header
when GITHUB_TOKEN is set
rootfs/root/docker/setup/05-custom.sh
Replaces go install for 11 tools with direct binary/tarball downloads
from GitHub releases. Build time drops from 2+ hours to ~10 minutes
and arm64 QEMU segfaults are eliminated (no Go compilation for goreleaser).
- rootfs/root/docker/setup/05-custom.sh: add _gh_latest/_install_tar/_install_bin
helpers; download goreleaser, golangci-lint, staticcheck, gofumpt,
gotestsum, ko, air, buf, goose, gopls, govulncheck from release artifacts;
keep go install only for goimports, stringer, dlv, gops, benchstat,
wire, mockgen, protoc-gen-go, protoc-gen-go-grpc; remove -p=1 arm64
workaround (no longer needed)
rootfs/root/docker/setup/05-custom.sh
- docker-compose: add `command: tail null` so the container stays alive
instead of running go-workflow and immediately exiting
- Persistence: remove stale symlinks table — those were created by the
old 00-go.sh which has been stripped; symlinks no longer exist
- Cross-compile: wrap bare `go build` examples in `docker run` so they
are valid as written, not ambiguous host-vs-container commands
- Development: fix clone URLs from casjaysdev/go to dockersrc/go
README.md
The arm64 build was failing with:
github.com/caarlos0/go-version: .../linux_arm64/compile: signal: segmentation fault
Under QEMU emulation, parallel Go package compilation creates memory
pressure that causes the compiler to crash. Setting GOFLAGS=-p=1 on
aarch64 serialises compilation, eliminating the segfault. amd64 builds
are unaffected (no flag set on x86_64).
- rootfs/root/docker/setup/05-custom.sh: detect aarch64 via uname -m
and export GOFLAGS=-p=1 before all go install calls
rootfs/root/docker/setup/05-custom.sh
Replace the service-lifecycle init script with a lean Go-native
default workflow. Running the container with no arguments now
automatically formats, vets, tests, and builds the mounted project.
- rootfs/usr/local/etc/docker/init.d/00-go.sh: stripped from 979
lines of daemon-lifecycle boilerplate to 3 lines that export
CONTAINER_INIT=yes and SERVICE_USES_PID=no, telling the init
framework this is a configuration-only container (no daemon, no
keep-alive loop)
- rootfs/usr/local/bin/go-workflow: new script — runs the canonical
Go workflow in order: go mod tidy → gofmt -w . → go vet ./... →
go test ./... → go build ./...; exits 1 with a clear usage message
if no go.mod is found in /app
- rootfs/usr/local/bin/entrypoint.sh: no-args path in both the init
block and the * case now exec go-workflow instead of __no_exit;
verified: default workflow, explicit commands, sh -c passthrough,
and missing go.mod error all behave correctly
- README.md: document the default workflow prominently; update
one-shot examples; add golangci-lint --timeout note; add tail null
long-running pattern
README.md
rootfs/usr/local/bin/entrypoint.sh
rootfs/usr/local/bin/go-workflow
rootfs/usr/local/etc/docker/init.d/00-go.sh
Root cause: entrypoint.sh unconditionally called `__no_exit` (an
`exec bash -c` monitoring loop) even when the user passed a command
to `docker run`, replacing the shell before the command could execute.
Secondary bug in `__exec_command`: used `bash --login -c "$cmdExec"`
which only captured the first word of the command and spawned a slow
login shell on every invocation.
- rootfs/usr/local/bin/entrypoint.sh: only call `__no_exit` when no
user command was given (`$# -eq 0`); otherwise fall through to the
case dispatch so `docker run ... go version` works correctly
- rootfs/usr/local/etc/docker/functions/entrypoint.sh: rewrite
`__exec_command` to use `exec "$@"` directly instead of wrapping
in `bash --login -c "$cmdExec"`; both files kept in sync
rootfs/usr/local/bin/entrypoint.sh
rootfs/usr/local/etc/docker/functions/entrypoint.sh
Add buf (modern protobuf toolchain) and goose (Go-native DB migration
runner) to complete the Go dev toolchain. Fix PATH order so baked image
tools in /usr/local/bin always take precedence over anything installed
at runtime into $GOPATH/bin. Full README rewrite following canonical
section order.
- Dockerfile: insert /usr/local/bin between /usr/local/go/bin and
/usr/local/share/go/bin in both build and final stage ENV PATH
- rootfs/etc/profile.d/go.sh: same PATH fix; explicit /usr/local/bin
guard added; $GOPATH/bin appended last
- rootfs/root/docker/setup/05-custom.sh: add buf@latest and
goose/v3@latest installs with descriptive comments
- README.md: full rewrite — H1 title, canonical section order (Pull,
Docker, Tools table, Env vars, PATH order, Persistence, Cross-compile,
Development, License); accurate tool list matching actual image content
Dockerfile
README.md
rootfs/etc/profile.d/go.sh
rootfs/root/docker/setup/05-custom.sh
Add two debug/profiling tools to 05-custom.sh:
- gops: live process diagnostics (list Go processes, dump stacks, force GC)
- benchstat: statistically sound benchmark comparison via pprof
Both tools baked into /usr/local/bin at build time.
Build verified: go1.26.3, all 19 tools confirmed on PATH.
- rootfs/root/docker/setup/05-custom.sh: add gops and benchstat installs
rootfs/root/docker/setup/05-custom.sh
Go is now downloaded from go.dev/dl at build time (always latest stable,
never pinned). All tools are baked into /usr/local/bin so they are on
PATH out of the box. Module cache and build cache live in the volumed
GOPATH (/usr/local/share/go) so they persist across container restarts
without re-downloading.
- Dockerfile: add /usr/local/go/bin to PATH in both build and final stage
- rootfs/root/docker/setup/05-custom.sh: full Go install + tool install
- detects arch (amd64/arm64/armv6l/386) via uname -m
- fetches latest stable version from go.dev/dl?mode=json via jq
- extracts to /usr/local/go; symlinks go+gofmt to /usr/local/bin
- installs with GOBIN=/usr/local/bin (baked into image, not in volume):
gopls, goimports, gofumpt, stringer, golangci-lint, staticcheck,
govulncheck, gotestsum, dlv, air, goreleaser, wire, mockgen (uber),
ko, protoc-gen-go, protoc-gen-go-grpc
- cleans modcache and build cache after install to keep layer lean
- rootfs/etc/profile.d/go.sh: add /usr/local/go/bin prepend so
interactive shells always find the Go distribution binaries first
Volume strategy:
/usr/local/share/go → GOPATH (module cache + build cache + user bins)
Mount a named volume or bind-mount here to avoid re-downloading modules:
docker run -v go-cache:/usr/local/share/go casjaysdevdocker/go
Dockerfile
rootfs/etc/profile.d/go.sh
rootfs/root/docker/setup/05-custom.sh
Sync project with updated upstream template files while preserving all
Go-specific customizations (EXEC_CMD_BIN='', DATA_DIR='', Go env vars,
/data/go symlink, Go-named dirs, etc.).
- .env.scripts: bump version stamp to 202605292219-git
- .gitattributes: update date stamp from template
- .gitea/workflows/docker.yaml: replace bare echo with printf %q for
GITHUB_OUTPUT assignments; use local var assignments for docker org/tag
- .gitignore: expand with editor configs (VSCode/JetBrains/Vim), AI tool
configs (Claude/.cursor/Copilot), env/secrets, build artifacts,
dependency dirs, logs, test/coverage dirs from updated template
- Dockerfile: bump BUILD_DATE to 202605292219; fix PHP_FPM detection from
ls subshell to set -- glob pattern; fix systemd cleanup to use for loop
instead of rm -f $(ls | grep -v ...)
- rootfs/root/docker/setup/00-init.sh through 07-cleanup.sh: update
version/date stamps to 202605292220-git template versions
- rootfs/usr/local/bin/entrypoint.sh: update stamp; fix echo quoting to
use double quotes for CONTAINER_NAME expansion
- rootfs/usr/local/etc/docker/init.d/00-go.sh: apply template improvements:
split SIGPWR trap onto separate line with 2>/dev/null || true; expand
debugger setup to multi-line if/else; use $(<file) instead of cat for
old_pid; apply _script_hash invalidation (W14) before START_SCRIPT
generation; replace heredoc/eval approach with printf %q for safe
quoting; launch START_SCRIPT with bash not eval sh -c (W15); add null
guard on chown in __run_secure_function; use _resolved temp var pattern
for type -P resolution (remove SERVICE_PID_NUMBER); explicit
[ "$1" = "check" ] guard on __check_service; errorCode=${PIPESTATUS[0]};
fix command -v pre check; add grep -- separator; fix ps|awk|grep chain
to pure awk; remove duplicate mkdir block
- rootfs/usr/local/share/template-files/config/env/default.sample:
ENTRYPOINT_PID_FILE path /run/.entrypoint.pid → /run/init.d/entrypoint.pid
- rootfs/usr/local/share/template-files/config/env/examples/zz-entrypoint.sh:
same ENTRYPOINT_PID_FILE path fix
Dockerfile
.env.scripts
.gitattributes
.gitea/workflows/docker.yaml
.gitignore
rootfs/root/docker/setup/00-init.sh
rootfs/root/docker/setup/01-system.sh
rootfs/root/docker/setup/02-packages.sh
rootfs/root/docker/setup/03-files.sh
rootfs/root/docker/setup/04-users.sh
rootfs/root/docker/setup/05-custom.sh
rootfs/root/docker/setup/06-post.sh
rootfs/root/docker/setup/07-cleanup.sh
rootfs/usr/local/bin/entrypoint.sh
rootfs/usr/local/etc/docker/init.d/00-go.sh
rootfs/usr/local/share/template-files/config/env/default.sample
rootfs/usr/local/share/template-files/config/env/examples/zz-entrypoint.sh
Update the embedded entrypoint copies in rootfs/ to match the
upstream template change. Internal state files renamed to dotfiles
so they're not matched by `/run/*.pid` cleanup globs:
- /run/init.d/entrypoint.pid -> /run/.entrypoint.pid
- /run/no_exit.pid -> /run/.no_exit.pid
- /run/backup.pid -> /run/.backup.pid
- /run/__start_init_scripts.pid -> /run/.start_init_scripts.pid
Per-service PIDs in /run/init.d/ are unchanged.
Dockerfile
.env.scripts
rootfs/usr/local/bin/copy
rootfs/usr/local/bin/entrypoint.sh
rootfs/usr/local/bin/healthcheck
rootfs/usr/local/bin/symlink
rootfs/usr/local/etc/docker/functions/entrypoint.sh
rootfs/usr/local/etc/docker/init.d/00-go.sh
rootfs/usr/local/share/template-files/config/env/default.sample
rootfs/usr/local/share/template-files/config/env/examples/zz-entrypoint.sh
Aligns README install/run snippets with the new convention split:
rootfs/ for Dockerfile-build content (image filesystem), volumes/
for docker-compose host bind-mounts. Compose mounts, host bind
paths, and runtime data dirs are renamed; Dockerfile COPY/ADD
sources (where present) are preserved.
README.md
- Rename ENV_IMAGE_NAME to ENV_REGISTRY_REPO and ENV_ORG_NAME to ENV_REGISTRY_ORG for consistent naming
- Clarify ENV_REGISTRY_URL to be the provider base URL and rename ENV_IMAGE_PUSH to ENV_REGISTRY_PUSH
- Extract install_go_tool helper to handle best-effort installs with periodic cache flushing every 5 tools
- Add GOMAXPROCS, GOMEMLIMIT, and GOFLAGS env vars for controlled Go build resource usage
- Clear test cache before module cache cleanup to reduce peak disk usage during image builds
.env.scripts
rootfs/root/docker/setup/05-custom.sh
- Replace casjaysdevdocker org references with casjaysdev across env config
- Update git repo URL to point to dockersrc/go
- Update registry and push destination URLs to casjaysdev/go
- Remove stale scheduled tasks lock file
.claude/scheduled_tasks.lock
.env.scripts