Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.git
.github
.cache
.gocache
.gotmp
.idea
.vscode

bin
coverage
dist
tmp

*.coverprofile
*.log
*.out
*.prof
*.test

.env
.env.*
config.yaml
config.local.yaml
config.*.local.yaml
configs/config.local.yaml

docs
tests

129 changes: 129 additions & 0 deletions .github/workflows/deploy-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
name: deploy-test

on:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: deploy-test
cancel-in-progress: false

env:
IMAGE_REPOSITORY: cixing-api

jobs:
deploy:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Validate required secrets
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
TEST_SERVER_HOST: ${{ secrets.TEST_SERVER_HOST }}
TEST_SERVER_USERNAME: ${{ secrets.TEST_SERVER_USERNAME }}
TEST_SERVER_SSH_KEY: ${{ secrets.TEST_SERVER_SSH_KEY }}
TEST_CONFIG_YAML: ${{ secrets.TEST_CONFIG_YAML }}
TEST_POSTGRES_PASSWORD: ${{ secrets.TEST_POSTGRES_PASSWORD }}
run: |
set -eu
test -n "$DOCKERHUB_USERNAME"
test -n "$DOCKERHUB_TOKEN"
test -n "$TEST_SERVER_HOST"
test -n "$TEST_SERVER_USERNAME"
test -n "$TEST_SERVER_SSH_KEY"
test -n "$TEST_CONFIG_YAML"
test -n "$TEST_POSTGRES_PASSWORD"

- name: Login to Docker Hub
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
run: |
set -eu
echo "$DOCKERHUB_TOKEN" | docker login docker.io -u "$DOCKERHUB_USERNAME" --password-stdin

- name: Build and push image
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
run: |
set -eu
IMAGE_SHA="docker.io/${DOCKERHUB_USERNAME}/${IMAGE_REPOSITORY}:${GITHUB_SHA}"
IMAGE_LATEST="docker.io/${DOCKERHUB_USERNAME}/${IMAGE_REPOSITORY}:test-latest"
docker build -f deploy/docker/Dockerfile.api -t "$IMAGE_SHA" -t "$IMAGE_LATEST" .
docker push "$IMAGE_SHA"
docker push "$IMAGE_LATEST"
echo "CIXING_IMAGE=$IMAGE_SHA" >> "$GITHUB_ENV"

- name: Prepare deployment files
env:
TEST_CONFIG_YAML: ${{ secrets.TEST_CONFIG_YAML }}
TEST_POSTGRES_PASSWORD: ${{ secrets.TEST_POSTGRES_PASSWORD }}
run: |
set -eu
mkdir -p /tmp/cixing-deploy
cp deploy/compose/docker-compose.prod.yml /tmp/cixing-deploy/docker-compose.yml
printf '%s' "$TEST_CONFIG_YAML" > /tmp/cixing-deploy/config.yaml
{
echo "CIXING_IMAGE=${CIXING_IMAGE}"
echo "POSTGRES_PASSWORD=${TEST_POSTGRES_PASSWORD}"
echo "API_PORT=8080"
echo "MAILPIT_UI_PORT=8025"
} > /tmp/cixing-deploy/deploy.env

- name: Configure SSH
env:
TEST_SERVER_HOST: ${{ secrets.TEST_SERVER_HOST }}
TEST_SERVER_SSH_KEY: ${{ secrets.TEST_SERVER_SSH_KEY }}
run: |
set -eu
mkdir -p ~/.ssh
printf '%s\n' "$TEST_SERVER_SSH_KEY" > ~/.ssh/id_test_server
chmod 600 ~/.ssh/id_test_server
ssh-keyscan -H "$TEST_SERVER_HOST" >> ~/.ssh/known_hosts

- name: Upload deployment files
env:
TEST_SERVER_HOST: ${{ secrets.TEST_SERVER_HOST }}
TEST_SERVER_USERNAME: ${{ secrets.TEST_SERVER_USERNAME }}
run: |
set -eu
ssh -i ~/.ssh/id_test_server "$TEST_SERVER_USERNAME@$TEST_SERVER_HOST" \
"sudo mkdir -p /opt/cixing/config && sudo chown -R $TEST_SERVER_USERNAME /opt/cixing"
scp -i ~/.ssh/id_test_server /tmp/cixing-deploy/docker-compose.yml \
"$TEST_SERVER_USERNAME@$TEST_SERVER_HOST:/opt/cixing/docker-compose.yml"
scp -i ~/.ssh/id_test_server /tmp/cixing-deploy/deploy.env \
"$TEST_SERVER_USERNAME@$TEST_SERVER_HOST:/opt/cixing/deploy.env"
scp -i ~/.ssh/id_test_server /tmp/cixing-deploy/config.yaml \
"$TEST_SERVER_USERNAME@$TEST_SERVER_HOST:/opt/cixing/config/config.yaml"

- name: Deploy on ECS
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
TEST_SERVER_HOST: ${{ secrets.TEST_SERVER_HOST }}
TEST_SERVER_USERNAME: ${{ secrets.TEST_SERVER_USERNAME }}
run: |
set -eu
echo "$DOCKERHUB_TOKEN" | ssh -i ~/.ssh/id_test_server "$TEST_SERVER_USERNAME@$TEST_SERVER_HOST" \
"if docker info >/dev/null 2>&1; then docker login docker.io -u '$DOCKERHUB_USERNAME' --password-stdin; else sudo docker login docker.io -u '$DOCKERHUB_USERNAME' --password-stdin; fi"
ssh -i ~/.ssh/id_test_server "$TEST_SERVER_USERNAME@$TEST_SERVER_HOST" <<'REMOTE'
set -eu
cd /opt/cixing
DOCKER="docker"
if ! docker info >/dev/null 2>&1; then
DOCKER="sudo docker"
fi
chmod 600 /opt/cixing/deploy.env /opt/cixing/config/config.yaml
$DOCKER compose --env-file /opt/cixing/deploy.env -f /opt/cixing/docker-compose.yml pull
$DOCKER compose --env-file /opt/cixing/deploy.env -f /opt/cixing/docker-compose.yml up -d postgres redis mailpit
$DOCKER compose --env-file /opt/cixing/deploy.env -f /opt/cixing/docker-compose.yml run --rm migrate up
$DOCKER compose --env-file /opt/cixing/deploy.env -f /opt/cixing/docker-compose.yml up -d api
$DOCKER compose --env-file /opt/cixing/deploy.env -f /opt/cixing/docker-compose.yml ps
$DOCKER compose --env-file /opt/cixing/deploy.env -f /opt/cixing/docker-compose.yml logs --tail=80 api
REMOTE
73 changes: 73 additions & 0 deletions deploy/compose/docker-compose.prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: cixing-prod

services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: cixing
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
volumes:
- cixing_pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d cixing"]
interval: 5s
timeout: 3s
retries: 20

redis:
image: redis:7-alpine
restart: unless-stopped
command: ["redis-server", "--appendonly", "yes"]
volumes:
- cixing_redis:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 20

mailpit:
image: axllent/mailpit:latest
restart: unless-stopped
ports:
- "127.0.0.1:${MAILPIT_UI_PORT:-8025}:8025"

migrate:
image: ${CIXING_IMAGE:?CIXING_IMAGE is required}
restart: "no"
entrypoint: ["/app/cixing-migrate"]
command: ["up"]
environment:
CONFIG_FILE: /app/config/config.yaml
volumes:
- /opt/cixing/config/config.yaml:/app/config/config.yaml:ro
depends_on:
postgres:
condition: service_healthy

api:
image: ${CIXING_IMAGE:?CIXING_IMAGE is required}
restart: unless-stopped
environment:
CONFIG_FILE: /app/config/config.yaml
volumes:
- /opt/cixing/config/config.yaml:/app/config/config.yaml:ro
ports:
- "${API_PORT:-8080}:8080"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/healthz >/dev/null"]
interval: 10s
timeout: 3s
retries: 12

volumes:
cixing_pgdata:
cixing_redis:

39 changes: 39 additions & 0 deletions deploy/docker/Dockerfile.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
ARG GO_VERSION=1.25.1

FROM golang:${GO_VERSION}-alpine AS builder

WORKDIR /src

COPY go.mod go.sum ./
RUN go mod download

COPY . .

ARG TARGETOS=linux
ARG TARGETARCH=amd64

RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
go build -trimpath -ldflags="-s -w" -o /out/cixing-api ./cmd/cixing-api && \
CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
go build -trimpath -ldflags="-s -w" -o /out/cixing-migrate ./cmd/cixing-migrate

FROM alpine:3.20

RUN apk add --no-cache ca-certificates tzdata && \
adduser -D -H -u 10001 cixing

WORKDIR /app

COPY --from=builder /out/cixing-api /app/cixing-api
COPY --from=builder /out/cixing-migrate /app/cixing-migrate
COPY --from=builder /src/db/migrations /app/db/migrations

RUN chown -R cixing:cixing /app

USER cixing

ENV CONFIG_FILE=/app/config/config.yaml

EXPOSE 8080

ENTRYPOINT ["/app/cixing-api"]
Loading