Add Docker deployment support

- Create Dockerfile with multi-stage build for optimized image size
- Add GitHub Actions workflow for automatic image publishing to ghcr.io
- Add PORT and HOST fallback defaults in config (8080 and 0.0.0.0)
- Update README with Docker Compose installation instructions
This commit is contained in:
2025-11-26 17:38:51 +01:00
parent 9f673bfca3
commit a71c5a6d76
4 changed files with 116 additions and 8 deletions
+42
View File
@@ -0,0 +1,42 @@
name: Docker Build and Push
on:
push:
branches: [ main ]
tags: [ 'v*' ]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v4
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+22
View File
@@ -0,0 +1,22 @@
FROM golang:1.24-alpine AS builder
RUN apk add --no-cache gcc musl-dev sqlite-dev
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -o apocapoc-api cmd/api/main.go
FROM alpine:latest
RUN apk --no-cache add ca-certificates sqlite-libs
WORKDIR /root/
COPY --from=builder /app/apocapoc-api .
EXPOSE 8080
CMD ["./apocapoc-api"]
+43
View File
@@ -13,6 +13,49 @@ Self-hosted habit tracking service with a clean, hexagonal architecture.
## Quick Start ## Quick Start
### Using Docker Compose (Recommended)
1. Create a `docker-compose.yml` file:
```yaml
services:
api:
image: ghcr.io/davidfolch/apocapoc-api:latest
ports:
- "8080:8080"
environment:
- DB_TYPE=sqlite
- DB_PATH=/data/apocapoc.db
- JWT_SECRET=YOUR_SECRET_HERE
- JWT_EXPIRY=24h
- REFRESH_TOKEN_EXPIRY=168h
- CORS_ORIGINS=http://localhost:3000
- DEFAULT_TIMEZONE=UTC
volumes:
- habit-data:/data
restart: unless-stopped
volumes:
habit-data:
```
2. **Important**: Replace `YOUR_SECRET_HERE` with a secure random string for `JWT_SECRET`
3. Start the service:
```bash
docker-compose up -d
```
The API will be available at `http://localhost:8080`
**Configuration options:**
- `JWT_SECRET`: **Required**. Use a long random string
- `JWT_EXPIRY`: Token expiration (e.g., `24h`, `48h`)
- `REFRESH_TOKEN_EXPIRY`: Refresh token expiration (e.g., `168h` = 7 days)
- `CORS_ORIGINS`: Comma-separated list of allowed origins
- `DEFAULT_TIMEZONE`: Timezone for date calculations (e.g., `UTC`, `Europe/Madrid`)
### Using the binary ### Using the binary
1. Download the latest release 1. Download the latest release
+9 -8
View File
@@ -25,8 +25,8 @@ func Load() (*Config, error) {
cfg := &Config{ cfg := &Config{
DBType: os.Getenv("DB_TYPE"), DBType: os.Getenv("DB_TYPE"),
DBPath: os.Getenv("DB_PATH"), DBPath: os.Getenv("DB_PATH"),
Port: os.Getenv("PORT"), Port: getEnvOrDefault("PORT", "8080"),
Host: os.Getenv("HOST"), Host: getEnvOrDefault("HOST", "0.0.0.0"),
JWTSecret: os.Getenv("JWT_SECRET"), JWTSecret: os.Getenv("JWT_SECRET"),
JWTExpiry: os.Getenv("JWT_EXPIRY"), JWTExpiry: os.Getenv("JWT_EXPIRY"),
RefreshTokenExpiry: os.Getenv("REFRESH_TOKEN_EXPIRY"), RefreshTokenExpiry: os.Getenv("REFRESH_TOKEN_EXPIRY"),
@@ -40,12 +40,6 @@ func Load() (*Config, error) {
if cfg.DBPath == "" { if cfg.DBPath == "" {
return nil, fmt.Errorf("DB_PATH is required") return nil, fmt.Errorf("DB_PATH is required")
} }
if cfg.Port == "" {
return nil, fmt.Errorf("PORT is required")
}
if cfg.Host == "" {
return nil, fmt.Errorf("HOST is required")
}
if cfg.JWTSecret == "" { if cfg.JWTSecret == "" {
return nil, fmt.Errorf("JWT_SECRET is required") return nil, fmt.Errorf("JWT_SECRET is required")
} }
@@ -64,3 +58,10 @@ func Load() (*Config, error) {
return cfg, nil return cfg, nil
} }
func getEnvOrDefault(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}