diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..948c625 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,35 @@ +# Git +.git +.gitignore + +# Build artifacts +tmp/ +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool +*.out + +# Dependency directories +vendor/ + +# IDE specific files +.idea/ +.vscode/ + +# Docker +Dockerfile +.dockerignore + +# Logs +*.log + +# Local configuration +test/ +tmp/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6c50be8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,43 @@ +# Build stage +FROM golang:1.24-alpine AS builder + +# Set working directory +WORKDIR /app + +# Copy go.mod and go.sum files +COPY go.mod go.sum ./ + +# Download dependencies +RUN go mod download + +# Copy the source code +COPY . . + +# Build the application +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o member-console . + +# Runtime stage +FROM alpine:latest + +# Create a non-root user and group +RUN addgroup -S appgroup && adduser -S appuser -G appgroup + +# Set the working directory +WORKDIR /app + +# Copy the binary from the builder stage +COPY --from=builder /app/member-console . + +# Set environment variables +ENV PORT=8080 \ + ENV=production + +# Expose the port the app runs on +EXPOSE 8080 + +# Switch to non-root user +USER appuser + +# Command to run the application +ENTRYPOINT ["/app/member-console"] +CMD ["start"] diff --git a/README.md b/README.md index cec94e8..4baed0e 100644 --- a/README.md +++ b/README.md @@ -23,3 +23,48 @@ Member console application for users to create, acccess, and manage their accoun - [ ] Subresource Integrity (SRI) for CDN assets - [ ] Serve HTMX assets not from CDN - [ ] Find out if timeout middleware is actually needed or if net/http handles it + +## Building and publishing container image + +Building and publishing the container image is done using Docker Buildx. This allows us to build multi-platform images for both ARM64 and AMD64 architectures. + +```bash +docker buildx build \ + --platform linux/arm64,linux/amd64 \ + -t git.coopcloud.tech/wiki-cafe/member-console:latest \ + -t git.coopcloud.tech/wiki-cafe/member-console:$(date +%Y-%m-%d) \ + --push \ + . +``` + +## Deploying image to production + +### Generating Secrets + +To generate secure values for `session-secret` and `csrf-secret`, use the following commands: + +For `session-secret` (a base64-encoded random string): + +```bash +openssl rand -base64 32 +``` + +Example output: + +``` +rJcniy2aWl3vwBcrMJfqsTL+Wys7EwDx/RC+DRrKcYg= +``` + +For `csrf-secret` (a 32-character hexadecimal string): + +```bash +openssl rand -hex 16 +``` + +Example output: + +``` +e157b42a5b608882179cb4ac69c12f84 +``` + +Ensure these secrets are securely stored and persisted for application use. \ No newline at end of file diff --git a/cmd/root.go b/cmd/root.go index 29f25b3..8a01302 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -19,6 +19,7 @@ package cmd import ( "fmt" "os" + "strings" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -67,19 +68,18 @@ func initConfig() { viper.SetConfigName("mc-config") // Use "mc-config" as the config file name } - viper.SetEnvPrefix("MC") // set environment variable prefix - viper.AutomaticEnv() // read in environment variables that match + viper.SetEnvPrefix("MC") // set environment variable prefix + viper.AutomaticEnv() // read in environment variables that match + viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) // Replace `-` with `_` in environment variable keys // If a config file is found, read it in. if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok { - // Config file not found. This is always an error. - fmt.Fprintln(os.Stderr, "Error: Config file not found:", err) - os.Exit(1) + // Config file not found. This is now a warning. + fmt.Fprintln(os.Stderr, "Warning: Config file not found. Proceeding with defaults.") } else { // Another error occurred (e.g., malformed YAML). This should be reported. fmt.Fprintln(os.Stderr, "Error reading config file:", err) - os.Exit(1) // Exit for other config read errors } } else { fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) diff --git a/mc-config.yaml b/mc-config.yaml deleted file mode 100644 index e69de29..0000000