- Go 94.2%
- Dockerfile 5.8%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
ci/woodpecker/push/change_log Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/tag/release Pipeline was successful
ci/woodpecker/cron/vulnerability Pipeline was successful
ci/woodpecker/cron/renovate Pipeline was successful
ci/woodpecker/cron/auto_merge Pipeline was successful
ci/woodpecker/cron/release Pipeline was successful
Merged automatically |
||
| internal/api | ||
| .gitignore | ||
| .goreleaser.yaml | ||
| .markdownlint.json | ||
| .prettierrc.json | ||
| .trivyignore | ||
| CLAUDE.md | ||
| Containerfile | ||
| go.mod | ||
| go.sum | ||
| main.go | ||
| openapi.go | ||
| openapi.yaml | ||
| README.md | ||
| renovate.json | ||
| server.go | ||
| store.go | ||
| store_test.go | ||
| swaggerui.go | ||
CI/CD Temporary Artifact Store
A small REST service for passing build artifacts between stages or jobs in a CI/CD environment. It stores uploaded artifacts on local disk and automatically deletes them 15 minutes after upload. It is meant to run only on trusted, local infrastructure: there is no TLS and no authentication.
This project was built with AI assistance (Claude Code). Review the code before relying on it in your own environment.
Features
- Simple HTTP API: upload with POST, download with GET.
- Artifacts expire automatically 15 minutes after their last upload.
- Local disk storage only, no external database or object store.
- Single static binary, no CGO, no runtime dependencies.
- Structured JSON logging.
- OpenAPI 3.1 spec and an interactive Swagger UI served by the binary itself.
API
POST /artifact?id=<id>: uploads the request body under the given id. Uploading to an id that already exists overwrites it and resets its 15 minute expiry.201 Createdon first upload.200 OKwhen an existing id was overwritten.400 Bad Requestifidis missing or invalid.
GET /artifact?id=<id>: downloads the stored bytes with the originalContent-Type.404 Not Foundif the id was never uploaded or has expired.400 Bad Requestifidis missing or invalid.
idmust be 1-128 characters, with no/or control characters, and not.or..(it's used directly as the on-disk filename, so this prevents path traversal while otherwise allowing anything a Linux filename can hold).
Example:
curl -X POST --data-binary @report.txt 'http://localhost:8080/artifact?id=report'
curl 'http://localhost:8080/artifact?id=report' -o report.txt
The full spec is served at /openapi.yaml, and an interactive Swagger UI is available at
/swagger/.
Configuration
The server is configured with environment variables. Both are required, there are no defaults.
| Variable | Description |
|---|---|
ARTIFACT_STORE_ADDR |
Listen address, for example :8080 |
ARTIFACT_STORE_DIR |
Directory to store artifacts in |
ARTIFACT_STORE_DIR can be a mounted volume. The application only clears a content subdirectory
under it on startup, never the directory itself.
Building
Build a single static binary:
CGO_ENABLED=0 go build .
Or with goreleaser:
goreleaser build --snapshot --clean --single-target
The binary has two subcommands:
artifact-store serve # start the HTTP server
artifact-store version # print the build version
Container image
A Containerfile is provided. It does not compile the binary, it only copies the binary already
built by goreleaser, from ./dist/artifact-store_linux_amd64_v1/. Build the binary first, then
build the image from the repository root:
goreleaser build --snapshot --clean --single-target
podman build -f Containerfile -t artifact-store:latest .
The image runs as a non-root user and has no default command, so serve must be passed explicitly:
podman run --rm -p 8080:8080 \
-e ARTIFACT_STORE_ADDR=:8080 \
-e ARTIFACT_STORE_DIR=/data \
-v artifact-data:/data \
artifact-store:latest serve
Running as a service with Podman Quadlet
Create /etc/containers/systemd/artifact-store.container:
[Unit]
Description=CI/CD artifact store
StartLimitBurst=5
StartLimitIntervalSec=90
[Container]
Exec=serve
Image=code.thinkaboutit.tech/pandora/artifact-store.goapp:latest
Volume=artifact-data:/data
PublishPort=8080:8080
Environment="ARTIFACT_STORE_ADDR=:8080"
Environment="ARTIFACT_STORE_DIR=/data"
[Service]
Restart=on-failure
RestartSec=2
[Install]
# If you want auto-start after reboot
WantedBy=default.target
Then reload systemd and start it:
systemctl daemon-reload
systemctl start artifact-store.service
Running as a systemd service on NixOS
See Pandora flakse.
Development
go build ./...
go vet ./...
go test ./...
golangci-lint run ./...
After changing openapi.yaml, regenerate the server code with:
go generate ./...
internal/api/api.gen.go is generated and must not be edited by hand.
License
GPL-3.0.