GZCTF can be deployed using Docker, which is the recommended deployment method for most use cases. This guide covers Docker deployment using Docker Compose.
Prerequisites
- Docker Engine 20.10 or later
- Docker Compose v2.0 or later
- PostgreSQL database (can be deployed via Docker Compose)
- (Optional) Redis for distributed caching and SignalR backplane
Understanding the Dockerfile
GZCTF uses a multi-stage Dockerfile based on .NET 10.0 Alpine images:
/home/daytona/workspace/source/src/GZCTF/Dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS build
ARG TARGETPLATFORM
COPY publish /build
RUN cp -r /build/${TARGETPLATFORM} /publish
FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS final
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false \
LC_ALL=en_US.UTF-8
WORKDIR /app
RUN apk add --update --no-cache wget libpcap icu-data-full icu-libs \
ca-certificates libgdiplus tzdata krb5-libs && \
update-ca-certificates
COPY --from=build --chown=$APP_UID:$APP_UID /publish .
EXPOSE 8080
HEALTHCHECK --interval=5m --timeout=3s --start-period=10s --retries=1 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/healthz || exit 1
ENTRYPOINT ["dotnet", "GZCTF.dll"]
Key Features
- Multi-platform support: Uses
TARGETPLATFORM build argument for cross-platform builds
- Alpine-based: Minimal image size with required dependencies (libpcap for traffic capture, ICU for globalization)
- Health checks: Built-in health endpoint at
/healthz on port 3000
- Port configuration: Application runs on port 8080, metrics/health on port 3000
Docker Compose Setup
Create Docker Compose file
Create a docker-compose.yml file for your deployment:
version: '3.8'
services:
gzctf:
image: gztime/gzctf:latest
container_name: gzctf
restart: unless-stopped
environment:
# Database connection
- GZCTF_ConnectionStrings__Database=Host=db;Port=5432;Database=gzctf;Username=gzctf;Password=<your-db-password>
# Redis cache (optional but recommended)
- GZCTF_ConnectionStrings__RedisCache=redis:6379
# Storage configuration
- GZCTF_ConnectionStrings__Storage=disk://path=/app/files
# Email configuration (optional)
- GZCTF_EmailConfig__UserName=<smtp-username>
- GZCTF_EmailConfig__Password=<smtp-password>
- [email protected]
- GZCTF_EmailConfig__SenderName=GZCTF Platform
- GZCTF_EmailConfig__Smtp__Host=smtp.example.com
- GZCTF_EmailConfig__Smtp__Port=587
# Container provider (for challenges)
- GZCTF_ContainerProvider__Type=Docker
- GZCTF_ContainerProvider__PublicEntry=<your-domain-or-ip>
- GZCTF_ContainerProvider__DockerConfig__Uri=unix:///var/run/docker.sock
volumes:
- ./files:/app/files
- ./logs:/app/logs
- /var/run/docker.sock:/var/run/docker.sock # For Docker challenge management
ports:
- "8080:8080" # Application port
- "3000:3000" # Metrics and health check port
depends_on:
- db
- redis
networks:
- gzctf-network
db:
image: postgres:16-alpine
container_name: gzctf-db
restart: unless-stopped
environment:
- POSTGRES_DB=gzctf
- POSTGRES_USER=gzctf
- POSTGRES_PASSWORD=<your-db-password>
volumes:
- ./data/db:/var/lib/postgresql/data
networks:
- gzctf-network
redis:
image: redis:7-alpine
container_name: gzctf-redis
restart: unless-stopped
volumes:
- ./data/redis:/data
networks:
- gzctf-network
networks:
gzctf-network:
driver: bridge
docker-compose.minimal.yml
# Minimal setup without Redis
version: '3.8'
services:
gzctf:
image: gztime/gzctf:latest
container_name: gzctf
restart: unless-stopped
environment:
- GZCTF_ConnectionStrings__Database=Host=db;Port=5432;Database=gzctf;Username=gzctf;Password=<your-db-password>
- GZCTF_ConnectionStrings__Storage=disk://path=/app/files
- GZCTF_ContainerProvider__Type=Docker
- GZCTF_ContainerProvider__DockerConfig__Uri=unix:///var/run/docker.sock
volumes:
- ./files:/app/files
- ./logs:/app/logs
- /var/run/docker.sock:/var/run/docker.sock
ports:
- "8080:8080"
- "3000:3000"
depends_on:
- db
networks:
- gzctf-network
db:
image: postgres:16-alpine
container_name: gzctf-db
restart: unless-stopped
environment:
- POSTGRES_DB=gzctf
- POSTGRES_USER=gzctf
- POSTGRES_PASSWORD=<your-db-password>
volumes:
- ./data/db:/var/lib/postgresql/data
networks:
- gzctf-network
networks:
gzctf-network:
driver: bridge
All configuration values can be set via environment variables using the GZCTF_ prefix. Use double underscores (__) to represent nested configuration keys.Example: ConnectionStrings:Database becomes GZCTF_ConnectionStrings__Database
Update the following values in your docker-compose.yml:
<your-db-password>: Secure password for PostgreSQL
<your-domain-or-ip>: Public IP or domain for challenge container access
SMTP settings for email notifications (optional)
Run the following command to start all services:
Check that all containers are running:
Check the health endpoint:
curl http://localhost:3000/healthz
docker compose logs -f gzctf
Storage Configuration
GZCTF supports multiple storage backends:
environment:
- GZCTF_ConnectionStrings__Storage=disk://path=/app/files
volumes:
- ./files:/app/files
Container Provider Configuration
Docker Provider
For challenge containers using Docker:
environment:
- GZCTF_ContainerProvider__Type=Docker
- GZCTF_ContainerProvider__PublicEntry=ctf.example.com
- GZCTF_ContainerProvider__DockerConfig__Uri=unix:///var/run/docker.sock
# Optional: Docker registry authentication
- GZCTF_ContainerProvider__DockerConfig__UserName=<registry-user>
- GZCTF_ContainerProvider__DockerConfig__Password=<registry-password>
volumes:
- /var/run/docker.sock:/var/run/docker.sock
Mounting the Docker socket (/var/run/docker.sock) gives GZCTF full access to the Docker daemon. Ensure proper security measures are in place.
Kubernetes Provider
For Kubernetes-based challenge deployment, see the Kubernetes Deployment guide.
Reverse Proxy Setup
It’s recommended to run GZCTF behind a reverse proxy like Nginx or Caddy for HTTPS termination and additional security.
Nginx Configuration
server {
listen 443 ssl http2;
server_name ctf.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
client_max_body_size 64M;
location / {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
Caddy Configuration
ctf.example.com {
reverse_proxy localhost:8080
encode gzip
}
When behind a reverse proxy, configure forwarded headers:
environment:
- GZCTF_ForwardedOptions__ForwardedHeaders=XForwardedFor,XForwardedProto
- GZCTF_ForwardedOptions__KnownProxies__0=172.18.0.1
Updating GZCTF
To update to the latest version:
# Pull the latest image
docker compose pull
# Restart services
docker compose up -d
Always backup your database and files before updating!
Backup and Restore
Backup Database
docker exec gzctf-db pg_dump -U gzctf gzctf > backup.sql
Restore Database
docker exec -i gzctf-db psql -U gzctf gzctf < backup.sql
Backup Files
tar -czf files-backup.tar.gz ./files
Troubleshooting
Database Connection Issues
If GZCTF fails to connect to the database:
- Verify the database is running:
docker compose ps db
- Check database logs:
docker compose logs db
- Verify connection string format
- Ensure the database has been initialized
Container Creation Failures
If challenge containers fail to start:
- Verify Docker socket is mounted correctly
- Check Docker daemon is running
- Verify
PublicEntry is correctly configured
- Check GZCTF logs:
docker compose logs gzctf
Permission Issues
If you encounter permission issues with mounted volumes:
# Fix ownership
sudo chown -R 1000:1000 ./files ./logs
Next Steps