diff --git a/config/makefile/db.mk b/config/makefile/db.mk index d6d9a870..c735a811 100644 --- a/config/makefile/db.mk +++ b/config/makefile/db.mk @@ -46,12 +46,8 @@ db\:delete: docker compose down -v --remove-orphans db\:chmod: - #ostgreSQL has a strict rule for security. The SSL private key file (server.key) cannot be owned by a regular user. - # When you mount the file from your host server, the file inside the container is still owned by your user (gocanto), - # not by root or the postgres user. PostgreSQL sees this as a security risk and refuses to start. - #sudo chown root:root ./database/infra/ssl/server.key - #sudo chmod 600 ./database/infra/ssl/server.key - chmod 600 $(DB_INFRA_SERVER_KEY) + sudo chmod 600 $(DB_INFRA_SERVER_KEY) + sudo chmod 644 $(DB_INFRA_SERVER_CRT) db\:secure: rm -f $(DB_INFRA_SERVER_CRT) $(DB_INFRA_SERVER_CSR) $(DB_INFRA_SERVER_KEY) diff --git a/docker-compose.yml b/docker-compose.yml index 81e6a536..b69d7468 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -110,10 +110,12 @@ services: context: . dockerfile: ./docker/dockerfile-api args: - - APP_VERSION=v1.0.0-release + - APP_VERSION=0.0.0.1 - APP_HOST_PORT=${ENV_HTTP_PORT} - APP_USER=${ENV_DOCKER_USER} - APP_GROUP=${ENV_DOCKER_USER_GROUP} + - APP_DIR=/app + - BINARY_NAME=oullin_api container_name: oullin_api restart: unless-stopped secrets: diff --git a/docker/dockerfile-api b/docker/dockerfile-api index b9d408c5..cea85ace 100644 --- a/docker/dockerfile-api +++ b/docker/dockerfile-api @@ -1,74 +1,87 @@ -# --- Build Arguments -ARG GO_VERSION=1.24 -ARG ALPINE_VERSION=latest - -ARG APP_VERSION="0.0.0-dev" +# +# ---------------------------------------------------------------------------------------------------------------------- +# GLOBAL BUILD ARGUMENTS +# ---------------------------------------------------------------------------------------------------------------------- +# +# These args can be overridden at build time with `--build-arg NAME=value`. +# Otherwise, you could use you docker-compose file to achive the same purpose. +# +# ---------------------------------------------------------------------------------------------------------------------- +# + +ARG APP_VERSION=0.0.0.1 ARG BUILD_TAGS="posts,experience,profile,projects,social,talks,gus,gocanto" +ARG BINARY_NAME=oullin_api -ARG BINARY_NAME=server -ARG APP_HOST_PORT=8080 +# Non-root user/group settings. ARG APP_USER=appuser ARG APP_GROUP=appgroup ARG APP_HOME=/home/${APP_USER} -ARG BUILD_DIR=/app +# Container runtime port. +ARG APP_HOST_PORT=8080 + +# Application directory inside container. +ARG APP_DIR=/app + +# Storage directories (relative to APP_DIR). ARG STORAGE_DIR=storage ARG LOGS_DIR=logs ARG MEDIA_DIR=media ARG FIXTURES_DIR=fixture -# --- Build Stage -FROM golang:${GO_VERSION}-alpine AS builder +# ---------------------------------------------------------------------------------------------------------------------- +# BUILDER STAGE +# ---------------------------------------------------------------------------------------------------------------------- +FROM golang:1.24-alpine AS builder -# --- Docker Args always go before ENV vars. -# Forwards build-time arguments into this specific stage so they can be referenced. -ARG BUILD_DIR +# Bring in the build args needed in this stage. +ARG APP_DIR ARG BINARY_NAME ARG APP_VERSION ARG BUILD_TAGS -# --- Go env vars. -# Tell Go to keep its module & build caches under /app -ENV GOPATH=${BUILD_DIR}/.gopath -ENV GOMODCACHE=${BUILD_DIR}/.gopath/pkg/mod -ENV GOCACHE=${BUILD_DIR}/.gocache -RUN mkdir -p ${GOMODCACHE} ${GOCACHE} +# Configure Go build cache and module cache under our APP_DIR. +ENV GOPATH=${APP_DIR}/.gopath +ENV GOMODCACHE=${APP_DIR}/.gopath/pkg/mod +ENV GOCACHE=${APP_DIR}/.gocache -# Install timezone data so Go’s time.* calls work correctly. -RUN #apk add --no-cache tzdata +# Create the Go module & build cache directories. +RUN mkdir -p ${GOMODCACHE} ${GOCACHE} -# Sets the primary working directory for this stage of the build. -WORKDIR ${BUILD_DIR} +# Set the working directory for the build. +WORKDIR ${APP_DIR} -# Copies the Go module definition files into the builder. -# This is done first to leverage Docker's layer caching. The subsequent -# 'go mod download' step will only be re-run if these files have changed. +# Copy Go module files and download dependencies. COPY go.mod go.sum ./ - RUN go mod download -# Copies the rest of the application's source code into the builder. +# Copy remaining source code into the builder. COPY . . -# Compiles the Go application into a single, statically-linked binary. -# -tags: Applies build constraints, allowing for conditional compilation. -# -o: Specifies the output path and name for the compiled binary. -# -ldflags: Provides flags to the linker. -# -s: Strips the symbol table, reducing binary size. -# -w: Strips DWARF debugging information, further reducing size. -# -X: Injects a value into a string variable at build time. Here, it sets -# the application's version by targeting the 'Version' variable in the 'main' package. -RUN CGO_ENABLED=0 go build -tags "${BUILD_TAGS}" -o ${BUILD_DIR}/${BINARY_NAME} -ldflags="-s -w -X main.Version=${APP_VERSION}" . - -# --- Final Stage -FROM alpine:${ALPINE_VERSION} -#USER root - -# Forwards build-time arguments into this final stage so they can be referenced. +# Compile a statically-linked binary. +# +# * CGO_ENABLED=0: disable CGO for static builds. +# * -tags: apply build tags. +# * -o: output binaries path/name. +# * -ldflags: strip symbols and inject version. +# +RUN CGO_ENABLED=0 go build \ + -tags "${BUILD_TAGS}" \ + -o ${APP_DIR}/${BINARY_NAME} \ + -ldflags="-s -w -X main.Version=${APP_VERSION}" \ + . + +# ---------------------------------------------------------------------------------------------------------------------- +# FINAL STAGE +# ---------------------------------------------------------------------------------------------------------------------- +FROM alpine:3.22 + +# Bring in the runtime args. ARG APP_USER ARG APP_GROUP -#ARG APP_HOME -ARG BUILD_DIR +ARG APP_HOME +ARG APP_DIR ARG BINARY_NAME ARG STORAGE_DIR ARG LOGS_DIR @@ -76,37 +89,56 @@ ARG MEDIA_DIR ARG FIXTURES_DIR ARG APP_HOST_PORT -# Creates a dedicated, non-root user and group to run the application with. -#RUN addgroup -S ${APP_GROUP} && adduser -S ${APP_USER} -G ${APP_GROUP} -h ${APP_HOME} - +# Install timezone data so Go’s time.* functions work correctly. RUN apk add --no-cache tzdata ENV TZ=Asia/Singapore -# Make sure the home exists & switch into it. -WORKDIR /app +# Create the system group for our non-root user. +RUN addgroup -S ${APP_GROUP} + +# Create the system user, assign to group, set its home. +RUN adduser -S ${APP_USER} \ + -G ${APP_GROUP} \ + -h ${APP_HOME} \ + ${APP_USER} + +# Ensure the user’s home directory actually exists. +RUN mkdir -p ${APP_HOME} + +# Switch to the application directory (implicitly creates it if missing). +WORKDIR ${APP_DIR} -# Creates the necessary storage directories inside the container. -RUN mkdir -p ${STORAGE_DIR}/${LOGS_DIR} ${STORAGE_DIR}/${MEDIA_DIR} -RUN mkdir -p ${STORAGE_DIR}/${FIXTURES_DIR} ${STORAGE_DIR}/${FIXTURES_DIR} +# Create storage subdirectories under the APP_DIR. +# +# * logs/: for application logs +# * media/: for uploaded/static media +# * fixture/: for static fixtures data +# +RUN mkdir -p ${STORAGE_DIR}/${LOGS_DIR} +RUN mkdir -p ${STORAGE_DIR}/${MEDIA_DIR} +RUN mkdir -p ${STORAGE_DIR}/${FIXTURES_DIR} -# Copies the 'fixture' files from the local project directory into the container. -COPY ${STORAGE_DIR}/${FIXTURES_DIR} ./${STORAGE_DIR}/${FIXTURES_DIR}/ +# Copy fixture files from builder stage. +COPY ${STORAGE_DIR}/${FIXTURES_DIR} ${STORAGE_DIR}/${FIXTURES_DIR}/ -# Copies the compiled application binary from the 'builder' stage. -COPY --from=builder ${BUILD_DIR}/${BINARY_NAME} ${BUILD_DIR}/ -RUN chmod +x ${BUILD_DIR}/${BINARY_NAME} +# Copy the compiled binary and mark it executable. +COPY --from=builder ${APP_DIR}/${BINARY_NAME} ${APP_DIR}/ +RUN chmod +x ${APP_DIR}/${BINARY_NAME} -# Copy timezone data from builder so Go’s time.* calls work correctly. -#RUN apk add --no-cache tzdata -#COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo +# Give ownership of home and app directories to our non-root user. +RUN chown -R ${APP_USER}:${APP_GROUP} ${APP_HOME} +RUN chown -R ${APP_USER}:${APP_GROUP} ${APP_DIR} -# Recursively sets the ownership of all files in the application's home directory. -#RUN chown -R ${APP_USER}:${APP_GROUP} ${APP_HOME} +# Switch to non-root user for all subsequent container processes. +USER ${APP_USER}:${APP_GROUP} -#USER ${APP_USER} +# Arguments do not exist at container runtime, so ${APP_DIR} and ${BINARY_NAME} are empty when the entryoint is called. +# Therefore, we need to send those values as environment variables for the shell to pick them up at runtime. +ENV APP_DIR=${APP_DIR} +ENV BINARY_NAME=${BINARY_NAME} +# Expose the application port. EXPOSE ${APP_HOST_PORT} -#CMD ["./server"] -#CMD ["/app/${BINARY_NAME}"] -ENTRYPOINT ["/app/server"] +# Launch the binary (shell-form so that environment variables are expanded). +ENTRYPOINT ["/bin/sh", "-c", "exec ${APP_DIR}/${BINARY_NAME}"]