diff --git a/.env.example b/.env.example index 2521ea30..795819fc 100644 --- a/.env.example +++ b/.env.example @@ -34,3 +34,7 @@ ENV_DB_URL="" # --- The logs directory for Caddy to persists its logs. CADDY_LOGS_PATH="./storage/logs/caddy" + +# --- Docker (Local envs) +ENV_DOCKER_USER="gocanto" +ENV_DOCKER_USER_GROUP="ggroup" diff --git a/.env.gh.example b/.env.gh.example new file mode 100644 index 00000000..4d18b68a --- /dev/null +++ b/.env.gh.example @@ -0,0 +1,4 @@ +ENV_HTTP_PORT=8080 +ENV_DOCKER_USER=gocanto +ENV_DOCKER_USER_GROUP=ggroup +CADDY_LOGS_PATH=./storage/logs/caddy diff --git a/.env.prod.example b/.env.prod.example new file mode 100644 index 00000000..eefcf7d5 --- /dev/null +++ b/.env.prod.example @@ -0,0 +1,7 @@ +# --- Caddy +CADDY_LOGS_DIR=./storage/logs/caddy + +# --- Database Secrets +DB_SECRET_USERNAME=./database/infra/secrets/pg_username +DB_SECRET_PASSWORD=./database/infra/secrets/pg_password +DB_SECRET_DBNAME=./database/infra/secrets/pg_dbname diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f5899383..c60fa083 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -44,10 +44,10 @@ jobs: run: echo "${{ secrets.ENV_FILE_CONTENT }}" > .env shell: bash - - name: Build with Makefile - run: make build:prod + - name: Build Release Images + run: make build:ci - - name: Log in to GitHub Container Registry + - name: Log in to GitHub Registry uses: docker/login-action@v3 with: registry: ghcr.io diff --git a/config/makefile/build.mk b/config/makefile/build.mk index ead8bf07..17c44ab3 100644 --- a/config/makefile/build.mk +++ b/config/makefile/build.mk @@ -1,4 +1,4 @@ -.PHONY: build\:local build\:prod build\:release +.PHONY: build\:local build\:prod build\:release build\:deploy BUILD_VERSION ?= latest BUILD_PACKAGE_OWNER := oullin @@ -6,19 +6,25 @@ BUILD_PACKAGE_OWNER := oullin build\:local: docker compose --profile local up --build -d +build\:ci: + @printf "\n$(CYAN)Building production images for CI$(NC)\n" + # This 'build' command only builds the images; it does not run them. + @docker compose --profile prod build + +# --- Deprecated +# We should always deploy builds from the CI and not build again in servers. build\:prod: - @printf "\n$(CYAN)docker compose --profile prod up --build -d$(NC)\n" - # --- The following lines take the variables passed to 'make' and export them - # into the shell environment for only the docker-compose command. - # These variable names now EXACTLY match what the Go application expects. - @POSTGRES_USER_SECRET_PATH="$(POSTGRES_USER_SECRET_PATH)" \ - POSTGRES_PASSWORD_SECRET_PATH="$(POSTGRES_PASSWORD_SECRET_PATH)" \ - POSTGRES_DB_SECRET_PATH="$(POSTGRES_DB_SECRET_PATH)" \ - ENV_DB_USER_NAME="$(ENV_DB_USER_NAME)" \ - ENV_DB_USER_PASSWORD="$(ENV_DB_USER_PASSWORD)" \ - ENV_DB_DATABASE_NAME="$(ENV_DB_DATABASE_NAME)" \ + @DB_SECRET_USERNAME="$(DB_SECRET_USERNAME)" \ + DB_SECRET_PASSWORD="$(DB_SECRET_PASSWORD)" \ + DB_SECRET_DBNAME="$(DB_SECRET_DBNAME)" \ docker compose --profile prod up --build -d +build\:deploy: + @DB_SECRET_USERNAME="$(DB_SECRET_USERNAME)" \ + DB_SECRET_PASSWORD="$(DB_SECRET_PASSWORD)" \ + DB_SECRET_DBNAME="$(DB_SECRET_DBNAME)" \ + docker compose --profile prod up -d + build\:release: @printf "\n$(YELLOW)Tagging images to be released.$(NC)\n" docker tag api-api ghcr.io/$(BUILD_PACKAGE_OWNER)/oullin_api:$(BUILD_VERSION) && \ diff --git a/database/infra/scripts/healthcheck.sh b/database/infra/scripts/healthcheck.sh index ad3bf8bf..856e848f 100755 --- a/database/infra/scripts/healthcheck.sh +++ b/database/infra/scripts/healthcheck.sh @@ -4,13 +4,13 @@ set -e # Read the secrets into variables. This is more robust than direct command substitution. -DB_USER=$(cat /run/secrets/postgres_user) -DB_NAME=$(cat /run/secrets/postgres_db) +DB_USER=$(cat /run/secrets/pg_username) +DB_NAME=$(cat /run/secrets/pg_dbname) # Explicitly check if the user variable is empty. If it is, fail immediately. # This prevents the "role -d does not exist" error. if [ -z "$DB_USER" ]; then - echo "Healthcheck Error: The postgres_user secret is empty or could not be read." >&2 + echo "Healthcheck Error: The pg_username secret is empty or could not be read." >&2 exit 1 fi diff --git a/database/infra/secrets/postgres_db b/database/infra/secrets/pg_dbname similarity index 100% rename from database/infra/secrets/postgres_db rename to database/infra/secrets/pg_dbname diff --git a/database/infra/secrets/postgres_password b/database/infra/secrets/pg_password similarity index 100% rename from database/infra/secrets/postgres_password rename to database/infra/secrets/pg_password diff --git a/database/infra/secrets/postgres_user b/database/infra/secrets/pg_username similarity index 100% rename from database/infra/secrets/postgres_user rename to database/infra/secrets/pg_username diff --git a/docker-compose.yml b/docker-compose.yml index bdd974ba..37e0540c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,11 @@ # Define the source of the secrets on the host machine. secrets: - postgres_user: - file: ${POSTGRES_USER_SECRET_PATH:-./database/infra/secrets/postgres_user} - postgres_password: - file: ${POSTGRES_PASSWORD_SECRET_PATH:-./database/infra/secrets/postgres_password} - postgres_db: - file: ${POSTGRES_DB_SECRET_PATH:-./database/infra/secrets/postgres_db} + pg_username: + file: ${DB_SECRET_USERNAME:-./database/infra/secrets/pg_username} + pg_password: + file: ${DB_SECRET_PASSWORD:-./database/infra/secrets/pg_password} + pg_dbname: + file: ${DB_SECRET_DBNAME:-./database/infra/secrets/pg_dbname} volumes: caddy_data: @@ -27,6 +27,7 @@ networks: services: caddy_prod: + image: api-caddy_prod build: context: ./caddy dockerfile: Dockerfile @@ -83,14 +84,15 @@ services: networks: - oullin_net secrets: - - postgres_user - - postgres_password - - postgres_db + - pg_username + - pg_password + - pg_dbname depends_on: api-db: condition: service_healthy api: + image: api-api env_file: - .env environment: @@ -109,9 +111,9 @@ services: container_name: oullin_api restart: unless-stopped secrets: - - postgres_user - - postgres_password - - postgres_db + - pg_username + - pg_password + - pg_dbname depends_on: api-db: condition: service_healthy @@ -130,9 +132,9 @@ services: - ./database/infra/migrations:/migrations - ./database/infra/scripts/run-migration.sh:/run-migration.sh secrets: - - postgres_user - - postgres_password - - postgres_db + - pg_username + - pg_password + - pg_dbname entrypoint: /run-migration.sh command: "" depends_on: @@ -157,9 +159,9 @@ services: # --- Use Docker Secrets instead of .env files for credentials. # The given postgres image automatically reads from files specified by these _FILE variables. environment: - POSTGRES_USER_FILE: /run/secrets/postgres_user - POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password - POSTGRES_DB_FILE: /run/secrets/postgres_db + POSTGRES_USER_FILE: /run/secrets/pg_username + POSTGRES_PASSWORD_FILE: /run/secrets/pg_password + POSTGRES_DB_FILE: /run/secrets/pg_dbname PGDATA: /var/lib/postgresql/data/pgdata # --- Securing port binding. @@ -173,19 +175,21 @@ services: # --- Define which secrets this service has access to. # These secrets are mounted securely in memory at /run/secrets/ secrets: - - postgres_user - - postgres_password - - postgres_db + - pg_username + - pg_password + - pg_dbname volumes: # Use a Docker Named Volume for data persistence. # This decouples my critical data from the host's file structure, making it # more robust, portable, and managed entirely by Docker. - oullin_db_data:/var/lib/postgresql/data + # Mount SSL certs and config files as read-only (:ro) for security. - ./database/infra/ssl/server.crt:/etc/ssl/certs/server.crt:ro - ./database/infra/ssl/server.key:/etc/ssl/private/server.key - ./database/infra/config/postgresql.conf:/etc/postgresql/postgresql.conf:ro - ./database/infra/scripts/healthcheck.sh:/healthcheck.sh:ro + command: postgres -c config_file=/etc/postgresql/postgresql.conf logging: