Skip to content

Commit e1df41d

Browse files
committed
add healthcheck and improve docker
1 parent 225828a commit e1df41d

9 files changed

Lines changed: 136 additions & 33 deletions

File tree

.env.example

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@ DB_MIN_POOL_SIZE=2
1313
DB_MAX_POOL_SIZE=5
1414
DB_QUERY_TIMEOUT_SEC=60
1515

16-
# To run migration on test database since
17-
# docker load only from .env file
18-
TEST_DB_NAME=goserver_test_db
19-
TEST_DB_USER=goserver_test_db_user
20-
TEST_DB_USER_PWD=changeit
16+
# PostgreSQL Docker container variables
17+
POSTGRES_DB=goserver_dev_db
18+
POSTGRES_USER=goserver_dev_db_user
19+
POSTGRES_PASSWORD=changeit
2120

2221
REDIS_HOST=redis
2322
REDIS_PORT=6379

.github/workflows/docker_compose.yml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,22 @@ jobs:
2222
run: docker compose build
2323
- name: Run docker images
2424
run: docker compose up -d
25+
- name: Wait for API health
26+
run: |
27+
echo "Waiting for API to become healthy..."
28+
for i in {1..30}; do
29+
STATUS=$(docker inspect --format='{{.State.Health.Status}}' goserver-postgres)
30+
if [ "$STATUS" = "healthy" ]; then
31+
echo "API is healthy"
32+
exit 0
33+
fi
34+
sleep 2
35+
done
36+
echo "API did not become healthy in time"
37+
docker logs goserver-postgres
38+
exit 1
2539
- name: Run tests
26-
run: docker exec -t goserve_example_api_server_postgres go test -v ./...
40+
run: docker exec -t goserver-postgres go test -v ./...
2741
- name: Clean up
2842
if: success() || failure()
2943
run: docker compose down --rmi all -v --remove-orphans

Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Use Go v1.25.6 as the base image
22
FROM golang:1.25.6-alpine
33

4+
RUN apk add --no-cache curl
5+
46
# Create a new user in the docker image
57
RUN adduser --disabled-password --gecos '' gouser
68

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ docker compose up --build
128128
**5. Run Tests**
129129

130130
```bash
131-
docker exec -t goserve_example_api_server_postgres go test -v ./...
131+
docker exec -t goserver-postgres go test -v ./...
132132
```
133133

134134
If having any issue
@@ -143,7 +143,7 @@ If having any issue
143143
go mod tidy
144144
```
145145

146-
Keep the docker container for `postgres` and `redis` running and **stop** the `goserve_example_api_server_postgres` docker container
146+
Keep the docker container for `postgres` and `redis` running and **stop** the `goserver-postgres` docker container
147147

148148
Change the following hosts in the **.env** and **.test.env**
149149

api/health/controller.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package health
2+
3+
import (
4+
"github.com/afteracademy/goserve/v2/network"
5+
"github.com/gin-gonic/gin"
6+
)
7+
8+
type controller struct {
9+
network.Controller
10+
service Service
11+
}
12+
13+
func NewController(
14+
service Service,
15+
) network.Controller {
16+
return &controller{
17+
Controller: network.NewController("/health", nil, nil),
18+
service: service,
19+
}
20+
}
21+
22+
func (c *controller) MountRoutes(group *gin.RouterGroup) {
23+
group.GET("/", c.getHealthHandler)
24+
}
25+
26+
func (c *controller) getHealthHandler(ctx *gin.Context) {
27+
health, err := c.service.CheckHealth()
28+
if err != nil {
29+
network.SendMixedError(ctx, err)
30+
return
31+
}
32+
33+
network.SendSuccessDataResponse(ctx, "success", health)
34+
}

api/health/dto/health_check.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package dto
2+
3+
import (
4+
"time"
5+
)
6+
7+
type HealthCheck struct {
8+
Timestamp time.Time `json:"timestamp" binding:"required"`
9+
Status string `json:"status" binding:"required"`
10+
}

api/health/service.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package health
2+
3+
import (
4+
"time"
5+
6+
"github.com/afteracademy/goserve-example-api-server-postgres/api/health/dto"
7+
)
8+
9+
type Service interface {
10+
CheckHealth() (*dto.HealthCheck, error)
11+
}
12+
13+
type service struct {
14+
}
15+
16+
func NewService() Service {
17+
return &service{}
18+
}
19+
20+
func (s *service) CheckHealth() (*dto.HealthCheck, error) {
21+
health := &dto.HealthCheck{
22+
Timestamp: time.Now(),
23+
Status: "OK",
24+
}
25+
return health, nil
26+
}

docker-compose.yml

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,19 @@ services:
33
build:
44
context: .
55
dockerfile: Dockerfile
6-
container_name: goserve_example_api_server_postgres
6+
container_name: goserver-postgres
77
restart: unless-stopped
88
env_file: .env
99
ports:
1010
- '${SERVER_PORT}:${SERVER_PORT}'
11+
healthcheck:
12+
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
13+
interval: 5s
14+
timeout: 3s
15+
retries: 10
16+
start_period: 10s
17+
networks:
18+
- goserve-postgres-network
1119
depends_on:
1220
postgres:
1321
condition: service_healthy
@@ -18,17 +26,15 @@ services:
1826
image: postgres:18.1
1927
restart: unless-stopped
2028
env_file: .env
21-
environment:
22-
POSTGRES_DB: ${DB_NAME}
23-
POSTGRES_USER: ${DB_USER}
24-
POSTGRES_PASSWORD: ${DB_USER_PWD}
2529
ports:
2630
- '${DB_PORT}:5432'
2731
volumes:
2832
- dbdata:/data/db
2933
# optional pg seed scripts
3034
- ./.extra/setup/init-test-db.sql:/docker-entrypoint-initdb.d/init-test-db.sql:ro
3135
- ./.extra/setup/pgseed.sql:/docker-entrypoint-initdb.d/pgseed.sql:ro
36+
networks:
37+
- goserve-postgres-network
3238
healthcheck:
3339
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$DB_NAME"]
3440
interval: 3s
@@ -44,6 +50,8 @@ services:
4450
command: redis-server --bind 0.0.0.0 --save 20 1 --loglevel warning --requirepass ${REDIS_PASSWORD}
4551
volumes:
4652
- cache:/data/cache
53+
networks:
54+
- goserve-postgres-network
4755
healthcheck:
4856
test: ["CMD-SHELL", "redis-cli -a ${REDIS_PASSWORD} ping | grep PONG"]
4957
interval: 5s
@@ -52,18 +60,23 @@ services:
5260

5361
migrate:
5462
image: migrate/migrate
55-
env_file: .env
63+
env_file: .test.env
5664
volumes:
5765
- ./migrations:/migrations
5866
depends_on:
5967
postgres:
6068
condition: service_healthy
69+
networks:
70+
- goserve-postgres-network
71+
entrypoint: ["/bin/sh", "-c"]
6172
command:
62-
[
63-
"-path", "/migrations",
64-
"-database", "postgres://${TEST_DB_USER}:${TEST_DB_USER_PWD}@postgres:5432/${TEST_DB_NAME}?sslmode=disable",
65-
"up"
66-
]
73+
- |
74+
migrate -path /migrations -database "postgres://$${DB_USER}:$${DB_USER_PWD}@postgres:5432/$${DB_NAME}?sslmode=disable" up
75+
76+
networks:
77+
goserve-postgres-network:
78+
driver: bridge
79+
6780
volumes:
6881
dbdata:
6982
cache:

startup/module.go

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/afteracademy/goserve-example-api-server-postgres/api/blog/editor"
1111
"github.com/afteracademy/goserve-example-api-server-postgres/api/blogs"
1212
"github.com/afteracademy/goserve-example-api-server-postgres/api/contact"
13+
"github.com/afteracademy/goserve-example-api-server-postgres/api/health"
1314
"github.com/afteracademy/goserve-example-api-server-postgres/api/user"
1415
"github.com/afteracademy/goserve-example-api-server-postgres/config"
1516
coreMW "github.com/afteracademy/goserve/v2/middleware"
@@ -21,13 +22,14 @@ import (
2122
type Module network.Module[module]
2223

2324
type module struct {
24-
Context context.Context
25-
Env *config.Env
26-
DB postgres.Database
27-
Store redis.Store
28-
UserService user.Service
29-
AuthService auth.Service
30-
BlogService blog.Service
25+
Context context.Context
26+
Env *config.Env
27+
DB postgres.Database
28+
Store redis.Store
29+
UserService user.Service
30+
AuthService auth.Service
31+
BlogService blog.Service
32+
HealthService health.Service
3133
}
3234

3335
func (m *module) GetInstance() *module {
@@ -36,6 +38,7 @@ func (m *module) GetInstance() *module {
3638

3739
func (m *module) Controllers() []network.Controller {
3840
return []network.Controller{
41+
health.NewController(m.HealthService),
3942
auth.NewController(m.AuthenticationProvider(), m.AuthorizationProvider(), m.AuthService),
4043
user.NewController(m.AuthenticationProvider(), m.AuthorizationProvider(), m.UserService),
4144
blog.NewController(m.AuthenticationProvider(), m.AuthorizationProvider(), m.BlogService),
@@ -66,14 +69,16 @@ func NewModule(context context.Context, env *config.Env, db postgres.Database, s
6669
userService := user.NewService(db)
6770
authService := auth.NewService(db, env, userService)
6871
blogService := blog.NewService(db, store, userService)
72+
healthService := health.NewService()
6973

7074
return &module{
71-
Context: context,
72-
Env: env,
73-
DB: db,
74-
Store: store,
75-
UserService: userService,
76-
AuthService: authService,
77-
BlogService: blogService,
75+
Context: context,
76+
Env: env,
77+
DB: db,
78+
Store: store,
79+
UserService: userService,
80+
AuthService: authService,
81+
BlogService: blogService,
82+
HealthService: healthService,
7883
}
7984
}

0 commit comments

Comments
 (0)