diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..01dc862 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,28 @@ +# syntax=docker/dockerfile:1.7 +ARG GO_VERSION=1.25.1 + +FROM golang:${GO_VERSION}-alpine AS build + +ARG TARGETOS +ARG TARGETARCH + +WORKDIR /src + +RUN apk add --no-cache build-base git + +COPY go.mod go.sum ./ +RUN go mod download +COPY . . + +RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64} go build -trimpath -ldflags="-s -w" -o /out/auth-server ./cmd/server + +FROM gcr.io/distroless/base-nonroot:latest + +WORKDIR /app + +COPY --from=build /out/auth-server ./auth-server + +USER nonroot:nonroot +EXPOSE 8000 + +ENTRYPOINT ["/app/auth-server"] diff --git a/Makefile b/Makefile index 6ffecde..4dac7ad 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ ifeq ($(DB_URL),) DB_URL := postgres://localhost/auth_dev?sslmode=disable endif -.PHONY: run dev build test fmt lint tidy clean migrate-status migrate-up migrate-down migrate-reset migrate-new sqlc-generate +.PHONY: run dev build test fmt lint tidy clean migrate-status migrate-up migrate-down migrate-reset migrate-new sqlc-generate compose-build run: go run ./cmd/server @@ -57,3 +57,6 @@ migrate-new: sqlc-generate: sqlc generate -f $(SQLC_CONFIG) + +compose-build: + docker compose build diff --git a/README.md b/README.md index 8ae76a3..e2ae7fb 100644 --- a/README.md +++ b/README.md @@ -52,10 +52,13 @@ Settings are sourced from environment variables (see [.env](./.env)). ## Database Tooling -Migrations live in [`internal/driver/db/migrations`](./internal/driver/db/migrations) and are managed with -[Goose](https://github.com/pressly/goose). Point `AUTH_DATABASE_URL` at your PostgreSQL instance—`postgres://localhost/auth_dev?sslmode=disable` -is a good local default—then use the Makefile helpers (`make migrate-up`, `make migrate-status`, etc.) to evolve the schema. The same DSN drives -[`sqlc`](https://sqlc.dev/) generation with `make sqlc-generate`, which reads [`internal/driver/db/sqlc.yaml`](./internal/driver/db/sqlc.yaml) and +Migrations live in [`internal/driver/db/migrations`](./internal/driver/db/migrations) +and are managed with [Goose](https://github.com/pressly/goose). +Point `AUTH_DATABASE_URL` at your PostgreSQL instance—`postgres://localhost/auth_dev?sslmode=disable` +is a good local default—then use the Makefile helpers +(`make migrate-up`, `make migrate-status`, etc.) to evolve the schema. +The same DSN drives [`sqlc`](https://sqlc.dev/) generation with `make sqlc-generate`, +which reads [`internal/driver/db/sqlc.yaml`](./internal/driver/db/sqlc.yaml) and emits typed data-access code alongside the queries. ## Project Layout @@ -75,6 +78,24 @@ emits typed data-access code alongside the queries. - [Alpine.js](https://alpinejs.dev/) — declarative client-side interactions. - [Pico.css](https://picocss.com/) — minimal, semantic-first styling. +## Deployment + +Use Docker Compose to run the application and its PostgreSQL dependency on a VPS. +The database service is kept on the private Compose network (no host port published). + +1. Provision secrets as environment variables + (or in an env file referenced via `docker compose --env-file`): + - `AUTH_SESSION_SECRET` must be a base64-encoded random value. + - `POSTGRES_PASSWORD` and optional `POSTGRES_USER`/`POSTGRES_DB` override the + database credentials referenced by `AUTH_DATABASE_URL`. + - Google OAuth values are optional but required for social login. +2. Build images with `make compose-build` (or `docker compose build`). +3. Start the stack in the background: `docker compose up -d`. +4. Monitor logs with `docker compose logs -f app`. + +To run administrative commands, exec into the containers +(e.g. `docker compose exec db psql`). + ## License MIT diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d541631 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,40 @@ +services: + app: + build: + context: . + dockerfile: Dockerfile + image: auth-server:latest + depends_on: + db: + condition: service_healthy + environment: + AUTH_LISTEN_ADDR: ":8000" + AUTH_ENV: ${AUTH_ENV:-production} + AUTH_LOG_MODE: ${AUTH_LOG_MODE:-json} + AUTH_SESSION_SECRET: ${AUTH_SESSION_SECRET:?set AUTH_SESSION_SECRET to a base64-encoded value} + AUTH_DATABASE_URL: postgres://${POSTGRES_USER:-auth_app}:${POSTGRES_PASSWORD:-change-me}@db:5432/${POSTGRES_DB:-auth}?sslmode=disable + AUTH_GOOGLE_CLIENT_ID: ${AUTH_GOOGLE_CLIENT_ID:-} + AUTH_GOOGLE_CLIENT_SECRET: ${AUTH_GOOGLE_CLIENT_SECRET:-} + AUTH_GOOGLE_REDIRECT_URL: ${AUTH_GOOGLE_REDIRECT_URL:-} + ports: + - "8000:8000" + restart: unless-stopped + + db: + image: postgres:16.4 + environment: + POSTGRES_DB: ${POSTGRES_DB:-auth} + POSTGRES_USER: ${POSTGRES_USER:-auth_app} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-change-me} + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + +volumes: + postgres_data: + driver: local